
Git 的价值不在于记住一串命令,而在于把“改文件”变成可检查、可回退、可协作的过程。它解决的是三个日常问题:不知道某次修改从何而来、不敢删除旧版本、多人同时修改时不知如何合并。
先建立正确的心智模型
Git 记录的是文件内容的演进,而不是替你复制一堆“最终版_v7”。每个提交(commit)都是一个可以回看的快照;分支让不同方案并行存在;远程仓库让团队共享这些快照。
最重要的是三块区域:
- 工作区:正在编辑的项目文件。
- 暂存区:下一次提交准备包含的改动清单。
- 本地仓库:已经落盘的提交历史。
因此,最常见的节奏不是“改完就上传”,而是:
修改文件 → 检查改动 → git add → git commit →(需要共享时)git push
暂存区的意义在于选择。一次工作中可能顺手改了格式、修了一个 bug、又调整了文档;将它们拆成语义单一的提交,未来定位问题和审查代码都会轻松很多。
Git、GitHub 与 origin
Git 是安装在本机的版本控制工具;GitHub、GitLab 或 Gitee 是托管远程仓库的平台。即使没有网络,Git 也能在本地提交、查看历史和创建分支。
克隆或连接远程仓库后,Git 通常把默认远程地址命名为 origin。它只是一个别名,不是特殊服务器:
git remote -v
git remote add origin <仓库地址>
本地历史和远程历史是两份独立状态。push 将本地提交发送到远程,fetch 获取远程变化但不改当前文件,pull 则相当于获取变化后尝试整合到当前分支。
第一个仓库:把状态看清楚
安装 Git 后,先设置提交作者信息:
git config --global user.name "你的名字"
git config --global user.email "你的邮箱@example.com"
在项目目录初始化仓库:
mkdir my-project
cd my-project
git init
.git 目录保存仓库对象和历史,不应手工删除或编辑。创建文件后,优先运行:
git status
它会告诉你哪些文件未被跟踪、哪些修改尚未暂存、哪些内容已经准备提交。对初学者来说,遇到不确定状态时先执行 git status,比盲目运行命令更可靠。
完成一次最小提交:
git add README.md
git commit -m "docs: add project readme"
git log --oneline
提交信息应描述“这次提交完成了什么”,例如 feat: add login form、fix: handle empty response。避免把无关变更混在同一提交中,也不要只写 update 或 fix bug。
分支是隔离实验的默认工具
分支不是高级功能,而是日常工作的安全边界。主分支保持可用;新功能、重构和试验性修改在自己的分支上完成:
git switch -c feature/search
# 修改、add、commit
git switch main
git merge feature/search
如果项目仍使用旧命令,也可用 git checkout -b feature/search 创建分支。合并前先确认自己所在分支:
git branch
git status
改到一半必须临时切换任务时,可以使用 git stash 暂存未提交的工作区改动,回来后用 git stash pop 恢复。它适合短期打断,不应替代正常提交。
冲突不是事故,是一次选择
当两组改动修改了同一段内容,Git 无法替你判断该保留哪一边,就会产生冲突。处理步骤很固定:
- 用
git status找到冲突文件。 - 打开文件,阅读
<<<<<<<、=======、>>>>>>>标记两侧的内容。 - 选择或重新组织最终内容,并删除这些标记。
- 运行测试或至少检查相关功能。
git add <文件>,再完成合并提交。
冲突解决的重点不是“让命令通过”,而是确认最终行为正确。冲突越早处理、提交越小,成本越低。
远程协作的稳妥流程
把现有本地项目推送到新建远程仓库时:
git branch -M main
git remote add origin <仓库地址>
git push -u origin main
加入已有项目时,从克隆开始:
git clone <仓库地址>
cd <仓库目录>
git switch -c feature/your-task
一个适合多数团队的循环是:先同步目标分支;在功能分支小步提交;推送分支;创建 Pull Request(PR);由他人审查、测试并合并。PR 的价值不仅是“点一下合并”,更是把改动范围、设计理由和测试证据集中起来。
如果 PR 打开期间 main 又前进了,先把主分支的新变化整合进你的分支,解决测试或冲突后再推送。团队应对“合并前是否 rebase”形成统一约定,避免各自混用而造成历史难读。
参与外部开源仓库时,通常先 Fork 到自己的账户,在 Fork 上开分支并提交 PR;不要直接向不属于自己的主仓库推送。
忽略不该进仓库的东西
.gitignore 用来排除依赖缓存、构建产物、日志和本地密钥等不应提交的文件:
node_modules/
dist/
.env
*.log
不要把真实密码、令牌或私钥提交到仓库;即使之后删除,历史中也可能仍然存在。已经被 Git 跟踪的文件不会仅因新增 .gitignore 自动停止跟踪,需要明确移出索引。
回退前先判断:改动在哪里?
所谓“撤销”取决于改动所在区域:
# 丢弃某个尚未暂存的文件修改
git restore <文件>
# 从暂存区撤下,但保留工作区改动
git restore --staged <文件>
# 查看提交与差异
git log --oneline
git diff
git diff --staged
对已经共享的提交,不要轻易用改写历史的方式“强行回去”。更安全的做法通常是 git revert <提交号>:它创建一个反向提交,保留完整可审计的历史。涉及 reset --hard、强制推送或批量文件删除前,先确认当前分支、远程状态,并保留可恢复的提交引用。
一份最常用的命令清单
git status # 当前状态
git add <文件> # 暂存指定改动
git add . # 暂存当前目录下改动(提交前先确认)
git commit -m "说明" # 创建提交
git log --oneline --graph # 简洁查看历史
git switch -c <分支名> # 创建并切换分支
git switch <分支名> # 切换分支
git merge <分支名> # 合并分支
git fetch origin # 获取远程更新
git pull --ff-only # 仅在可快进时拉取,避免意外合并
git push -u origin <分支> # 首次推送分支并设置追踪关系
最后,把 Git 当作工作习惯,而非出问题后的补救工具:开始任务先看状态;每完成一个可解释的小单元就提交;共享前检查差异;合并前阅读代码和测试结果。这样版本历史才会成为帮助团队理解项目的资产。
为什么“复制一个最终版”迟早会失控
没有版本控制时,最常见的做法是给文件改名:报告_最终版、报告_最终版2、报告_最终确认版。短时间内这看起来很省事,真正麻烦的是它没有回答三个关键问题:这两个版本到底差了什么?哪一个曾经通过测试?谁在什么背景下改过这一段?
Git 不要求你保存整份文件副本,而是把每次明确的改动组织成提交。提交包含文件快照、作者、时间、说明和与前一个提交的关系。它带来的不是“无限撤销”,而是可追溯性:先能看见差异,才谈得上安全地回退或合并。
这也解释了 Git 更擅长源代码、配置、Markdown、文本数据等内容。它可以逐行比较文本变化;图片、视频、压缩包等二进制文件通常只能被视为整体替换。二进制资产并非不能放进 Git,但需要控制体积,并在必要时采用 Git LFS 或对象存储。
配置完成后,先认识几个常用视图
除了 git status,还有三条命令能帮助你在操作前看清楚发生了什么:
git diff # 工作区与暂存区的差异
git diff --staged # 暂存区与上一次提交的差异
git log --oneline --graph --decorate --all
第一条回答“我刚才改了哪些内容”;第二条回答“下一次提交实际会带走哪些内容”;第三条把分支和提交关系压缩成容易阅读的图形。养成在 add 之前看 git diff、在 commit 之前看 git diff --staged 的习惯,可以避免把调试输出、临时文件或无关改动一起提交。
如果只想暂存一个文件,用 git add path/to/file;如果只想暂存文件中的一部分,可以使用:
git add -p
Git 会把改动分成若干块逐一询问。这个能力很适合“一个文件中混有两个任务”的情况:把真正修复问题的部分作为一次提交,把纯格式调整作为另一次提交。提交越小、意图越集中,后续的代码审查、回滚和定位问题就越容易。
一次完整的小任务应该怎样提交
假设要为项目增加一个欢迎页面。一个稳妥的过程可以是:先从主分支同步最新代码;创建功能分支;完成最小可运行版本;查看差异并测试;提交;推送并创建 PR。
git switch main
git pull --ff-only
git switch -c feature/welcome-page
# 编辑文件、运行测试
git status
git add src/pages/welcome.tsx src/styles/welcome.css
git diff --staged
git commit -m "feat: add welcome page"
git push -u origin feature/welcome-page
这里每一步都有明确目的。switch main 确认起点,pull --ff-only 避免在不知情时制造合并提交,switch -c 隔离新工作,git add 选择提交范围,push -u 建立本地分支与远程分支的追踪关系。以后在该分支上只需 git push 即可。
提交说明推荐采用“类型:动作对象”的形式,例如 docs: clarify setup steps、fix: guard empty input、refactor: extract request client。不必追求统一的英文格式,但应让三个月后的自己仍然知道这次提交做了什么、为何而做。若一次改动很大,可以先拆分为可独立理解的多个提交,而不是用一条模糊的“完成开发”掩盖全部细节。
分支、HEAD 与合并到底在做什么
可以把分支理解成“指向某个提交的可移动标签”。main 只是一个通常被用作稳定主线的分支名;它没有魔法。HEAD 则表示你当前检出的提交或分支位置。新建分支时,两个分支起初指向同一提交;之后你在新分支提交,只有新分支的指针向前移动。
当 Git 能直接把目标分支指针向前移动时,合并是 fast-forward;当两条分支都各自产生了提交,Git 会尝试创建一个合并结果。无论使用 merge 还是 rebase,都要先理解团队约定:
- merge 保留分支曾经分开又汇合的历史,适合共享分支上的常规整合;
- rebase 把本地未共享的提交接到新基点之后,使历史更线性;
- 已经被他人基于其开发的公共提交,不应随意 rebase 后强推,因为提交标识会改变。
对于初学者,先在功能分支上使用普通合并即可。只有当团队明确要求线性历史、并且理解 rebase 的影响范围时,再把它纳入日常流程。
暂存工作与切换任务
临时收到线上问题时,理想情况是先将当前工作做成一个小提交;若它尚未达到可提交状态,可以使用 stash:
git stash push -m "wip: welcome page layout"
git switch main
# 处理紧急任务并提交
git switch feature/welcome-page
git stash pop
stash 是一个临时抽屉,不是长期仓库。积累太多 stash 会让人忘记其中装了什么,也可能在恢复时发生冲突。对于需要跨天保留的工作,更推荐创建一个标有 wip: 的本地提交,或者在功能分支上提交已经完整的部分。
冲突处理的实际顺序
冲突并不代表仓库损坏,它只表示两个变更同时触及了 Git 无法自动判断的同一片区域。解决时不要急着删除标记,先理解两边改动各自意图。常见标记形式如下:
<<<<<<< 当前分支
当前分支的内容
=======
另一分支的内容
>>>>>>> 另一分支
最终内容可能是选择其中一边、组合两边,或者重新写一个更符合现在需求的版本。编辑完成后,检查标记是否全部删除,运行与该模块相关的测试,再执行 git add。如果是合并操作,Git 会在全部冲突解决后允许创建合并提交;如果是 rebase,可用 git rebase --continue 继续。判断失误时不要硬撑:合并过程可用 git merge --abort 退出,rebase 过程可用 git rebase --abort 回到开始前的状态。
远程协作不等于不断执行 pull
远程仓库是共享参考点,但本地工作应保持可控。一个有用的区分是:
git fetch origin # 只下载远程信息,不动当前文件
git status # 查看本地与上游的关系
git pull --ff-only # 仅在能直接前进时更新当前分支
fetch 适合先了解远程发生了什么,再决定是否整合;pull 是 fetch 加整合,使用 --ff-only 可以避免未经确认就生成合并提交。协作时应尽量避免直接在 main 上开发:从最新主分支创建功能分支,完成后通过 PR 合并。这样每个人的未完成工作互不干扰,主分支始终更接近可发布状态。
一份好的 PR 应包含:改动目的、影响范围、验证方式、需要审查者特别关注的地方。审查时不要只看代码是否“像是对的”,还应检查边界条件、错误处理、测试覆盖、文档和迁移影响。PR 打开后主分支继续变化是常态;在合并前同步并解决新出现的问题,通常比合并后再补救成本更低。
.gitignore 与敏感信息的边界
.gitignore 只能阻止未被跟踪的文件进入后续 git add,不能抹去已经提交过的秘密。常见规则包括:
.env
.env.*
node_modules/
dist/
coverage/
*.log
!.env.example
将 .env.example 提交进仓库、仅保留变量名和示例值,是向团队说明配置项的好方法;真实令牌应使用本机环境变量、密钥管理服务或部署平台的 Secret 配置。若凭据已经推送到远程,第一步是立即废弃或轮换它,而不是只删除文件。之后再根据影响范围清理历史和通知相关人员。
回退之前先确认目标与影响范围
Git 的“后悔药”有不同强度。对于尚未提交的单个文件,可使用 git restore <文件>;对于误暂存的文件,可使用 git restore --staged <文件>。两者都应先通过 diff 确认,尤其是前者会丢弃工作区中未保存到提交的内容。
已经提交但尚未共享的本地工作,有时可以用 reset 调整历史;但对已经推送、可能被同事拉取的提交,更推荐 git revert。它创建一个反向提交,保留“曾经做过、后来撤回”的事实,便于审计,也不会破坏他人的提交基线。
git reset --hard 和 git push --force 都是高影响命令。它们并非绝对不能用,但应先回答:当前分支是否只属于自己?要丢弃的改动是否已有备份?是否会改写他人依赖的远程历史?如果答案不确定,先执行 git status、git log,或创建一个临时分支保存当前指针,再继续操作。
给日常工作的检查清单
开始任务前:确认当前分支,拉取或 fetch 最新主分支,不要在未知状态上直接修改。开发过程中:让每个提交对应一个可解释的小目标,频繁查看 status 和 diff。提交前:检查暂存区、运行测试、确认没有密钥或生成文件。推送前:确认目标远程和分支名称。创建 PR 后:写清楚验证方式,及时处理审查意见和主分支的新变化。
当这些动作变成习惯,Git 就不再是一套需要背诵的命令,而是一个让个人工作可恢复、让团队协作可预测的基础设施。
最重要的原则其实很朴素:让每一次变化都保留清晰的来处和去处。提交前花一分钟检查,比事后花半天追查更划算;遇到不确定的操作,先创建分支或查看差异,比直接覆盖历史更安全。掌握这套节奏后,工具会逐渐退到幕后,留下的是对代码、协作和交付都更踏实的掌控感。
FIELD NOTES / DISCUSS
文章讨论
读完后,欢迎留下你的补充、疑问或不同看法。
正在读取评论…