Git 基础命令:从混乱到稳定的日常工作流
你修改了几个文件,输入 git add .,再运行 git commit。提交完成后才发现:调试日志也进去了,真正该提交的一处修改反而漏掉了。于是你开始怀疑 Git 很复杂。
其实,绝大多数 Git 困惑并不是命令太多,而是没有看清改动此刻位于哪里。Git 不是"保存文件"的工具;它是在有意识地保存==一份份可回到、可比较、可协作的快照==。
本文不追求覆盖所有命令,只解决日常开发最常见的四件事:看懂状态、提交正确的改动、与远程同步,以及在出错时安全地退回来。
先记住这一句话 Git 的日常工作流是:在工作区修改 → 选择性暂存 → 提交到本地历史 → 推送到远程。
Git 的四层:所有命令都在移动改动
把 Git 想成四个相连的位置,而不要把它想成一组零散命令。

| 位置 | 它回答的问题 | 常用命令 |
|---|---|---|
| 工作区 | 我刚刚改了什么? | git status、git diff |
| 暂存区 | 下一次提交里将包含什么? | git diff --staged |
| 本地仓库 | 我已经留下了哪些历史? | git log --oneline |
| 远程仓库 | 协作者或 GitHub 上发生了什么? | git fetch、git push |
这个模型能解释最容易让人困惑的一点:git add 并不是"把文件加进项目",而是"把文件==当前这一刻==的内容放入下一次提交"。在你 git add 后继续修改同一文件,后续修改仍留在工作区,不会自动进入这次提交。
官方《Pro Git》也把文件状态概括为"已跟踪/未跟踪、未修改/已修改/已暂存";每次提交记录的只是暂存区中的快照。阅读官方中文章节
第一习惯:不确定时,先看 git status
很多 Git 事故来自"以为自己在某个分支"或"以为改动已经提交"。git status 就像仪表盘,会显示:
- 当前处于哪个分支;
- 哪些文件尚未被 Git 跟踪;
- 哪些改动未暂存;
- 哪些改动已经准备提交;
- 当前分支和远程分支是否不同步。
bash
git status
把它放在每一个关键动作前:开始工作前、提交前、拉取远程更新前,以及任何报错后。它几乎从不修改文件,却常常让你避免一次不必要的操作。
!tip 一个简单判断 看见
working tree clean只表示工作区没有未提交改动;它不代表你已经push,也不代表远程没有新提交。仍需留意状态中关于远程分支的提示。
一次可靠的提交,不需要 git add .
假设你正在给一个工具补充 macOS 使用说明,同时本地还有一个未完成的实验。理想的提交只应包含说明文档,而不是整个目录的所有变化。
bash
# 1. 查看本次工作涉及哪些改动
git status
git diff
# 2. 精确暂存准备提交的文件
git add apps/captain-list-tool/README.md
# 3. 再次确认即将进入历史的内容
git diff --staged
# 4. 用清晰的提交信息保存快照
git commit -m "docs(captain-list): 补充 macOS 使用说明"
这四步略显"多余",但每一步都在回答一个不同的问题:改了什么、选了什么、将提交什么、这次提交为什么存在。
| 命令 | 它比较的内容 | 什么时候看 |
|---|---|---|
git diff |
工作区 ↔ 暂存区 | add 之前,检查尚未暂存的变化 |
git diff --staged |
暂存区 ↔ 最近一次提交 | commit 之前,检查即将写进历史的内容 |
!note
git add .不是禁令 当你确认当前目录下的全部改动都属于同一个原子提交时,git add .很高效。问题不在命令本身,而在于它跳过了"选择"这一步。任何有未完成实验、缓存文件或多个任务并行的场景,都应优先显式指定文件。
提交信息如何命名,见 \[Git 提交规范];"什么情况下要新建分支",见 \[从 Git Flow 到轻量分支策略]。
同步远程:先了解,再合入
远程仓库是协作和备份的位置,但 pull 不只是"下载"。它通常包含两件事:获取远程提交,再把它们合入当前分支。因此在不确定局面里,先拆开操作更稳妥。
bash
# 只获取远程状态,不改动工作区
git fetch origin
# 观察本地与远程提交图
git log --oneline --graph --decorate --all -n 20
# 确认本地可直接快进后再更新
git pull --ff-only
# 将自己的本地提交推送出去
git push
git pull --ff-only 的价值是:当本地和远程产生分叉时,它会停下来,而不会默默替你创建一个合并提交。此时先弄清分叉原因,再选择合并或变基,通常比事后整理历史轻松得多。
首次把一个新任务分支推到远程时,使用:
bash
git push -u origin feat/add-export
其中 -u 用来建立上游关联。之后在这个分支上,普通的 git push 和 git pull 就足够了。
分支不是"高级功能",而是隔离正在进行的工作
一项还没完成的功能、一个有风险的修复或一个尚待验证的实验,都不应让 main 长期处在半成品状态。分支提供了一条独立的提交路径,完成后再合入主线。
bash
# 从最新 main 开始一个独立任务
git switch main
git pull --ff-only
git switch -c feat/add-export
# 工作、提交、验证完成后回到主线
git switch main
git merge --no-ff feat/add-export
git branch -d feat/add-export
对个人项目和大多数持续交付项目,main 加短生命周期的 feat/*、fix/* 分支就足够了。不要因为听过 Git Flow,就立即维护 develop、release/*、hotfix/* 等长期流程;它们只适合确实需要多版本维护或发布冻结的团队。
出错了怎么办:先保护数据,再选择撤销方式
Git 的"撤销"有不同含义。最重要的判断是:改动有没有提交?是否已经推送给别人?
| 场景 | 推荐做法 | 为什么 |
|---|---|---|
| 不想保留某个未暂存文件的修改 | git restore <文件> |
只还原这个文件的工作区 |
| 想取消暂存、但保留修改 | git restore --staged <文件> |
改动仍在工作区,可重新选择 |
| 已经提交并推送,想反向撤销 | git revert <提交号> |
新增一条反向提交,不破坏共享历史 |
| 暂时切换任务,未提交改动又不想提交 | git stash push -m "说明" |
临时收起工作,之后可恢复 |
!danger 不把破坏性命令当作日常工具
git reset --hard会丢弃未提交内容;git push --force会重写远程历史。它们并非永远不能用,但应先确认目标提交、备份需求和协作者状态。对于已推送的共同历史,优先考虑git revert。
.gitignore:让 Git 从一开始就不看无关文件
Git 不应记录所有存在于电脑里的文件。系统生成物、构建结果、本地缓存、密钥、私密资料和可再生成数据都应在 .gitignore 中排除。
gitignore
# macOS
.DS_Store
# 本地环境与密钥
.env
*.log
# 构建与缓存
dist/
__pycache__/
有一个常被忽略的限制:.gitignore 只对==尚未跟踪==的文件生效。若文件已经被提交过,需要先确认它是否应从版本库索引中移除;不要以为添加忽略规则后它会自动消失。
把 Git 学成一套可重复的动作
到这里,日常工作可以压缩成一条清晰的链路:
text
开始前:git status → git pull --ff-only
工作中:git diff → git add <文件> → git diff --staged
完成后:git commit → git push
这条链路并不会替你处理所有合并冲突,也不会让你立刻掌握 rebase 或 cherry-pick。但它足以让绝大多数项目维护工作变得可预测:你知道自己在哪个分支、哪些内容会被提交、远程是否已经变化,以及出错时该先停下来查看什么。
下一步,不要继续背命令。到 Learn Git Branching(简体中文) 完成 Introduction Sequence,用可视化方式练习提交、分支与合并;再回到 \[从 Git Flow 到轻量分支策略],决定你的项目需要多复杂的协作流程。