资讯详情

资讯详情

Git 核心指令全解析:从基础操作到分支合并与回滚

git 大概是程序员电脑里被吐槽最多、却又最离不开的工具。平时不觉得它多重要等 merge 冲突铺满整屏或者提交记录乱成一锅粥的时候才想起当初应该把基本指令用明白。写这篇东西的初衷很简单我在好几个项目里见过太多人拿着一套固定指令凑合着用遇到分支、回滚、远程同步这些场景就开始发怵最后要么翻一堆零散文章要么用笨办法折腾半天。这次我把日常开发里真正高频的 git 使用指令从头到尾梳理了一遍原理和踩坑记录一起讲适合刚接触 git 的入门者也适合那些已经写了一阵子代码、但还没系统整理过 git 知识的同学。1. Git 日常使用的核心心智模型1.1 三个工作区域别再把 git 当成黑盒很多教程喜欢直接从命令行讲起导致新手总在背指令却不知道每条指令到底动了什么。我自己的经验是先记住 git 管理代码的三个区域再去记指令会轻松得多。工作区working directory你电脑里能直接看到的文件夹你在这里写代码、改文件。暂存区staging area / index可以理解成一个打包盒你把想要提交的文件先扔进这个盒子里git 会记录文件名和内容快照。本地仓库local repository暂存区里的东西被正式归档生成一条提交记录形成一个版本节点。还有一个远程仓库remote说白了就是放在服务器上的一份仓库副本本地仓库通过 push 和 pull 和它同步。如果你打开一个项目目录看到git status显示一堆Changes not staged for commit就说明工作区有改动还没进暂存区显示Changes to be committed说明已经在打包盒里了但没有生成提交记录。脑子里有了这个区域模型绝大多数 git 状态提示都能秒懂。1.2 为什么非要设计一个暂存区我刚开始用 git 的时候很困惑既然工作区改了文件为什么不能直接提交非要 add 一下再 commit 一下多此一举吗后来在一个写代码经常改一个功能顺便动了另一个功能的场景里我才彻底理解暂存区的价值。暂存区给了你一个挑选内容的机会。你改了 A 模块和 B 模块的代码但想把它们做成两次逻辑独立的提交就可以git add只选择 A 模块的文件先提交一次再把 B 模块的文件加进去提交第二次。如果没有中间这个打包盒每次提交就只能把当前所有改动全部打包提交历史会变得非常粗糙。用生活化的方式理解你在厨房做了三道菜不能因为都做好了就一起倒进一个碗里端上桌。你需要先把第一道菜装进盘子端上去再回来装第二道菜。暂存区就是那个盘子add是装盘commit是端上桌。2. 入门必用的基础指令把地基打牢2.1 初始化项目init 与 clone 的正确打开方式创建一个新项目最常用的是git init。它会在当前目录里生成一个.git文件夹这个文件夹里保存了仓库的全部元数据。注意.git文件夹不要手动翻着改里面全是内部结构改坏了仓库基本就废了。cd my-project git init还有一个高频场景是从远程仓库拉取项目。git clone会把远程仓库完整复制到本地同时自动配置好一个名为origin的远程仓库地址还会为本地分支建立跟踪关系。git clone gitgithub.com:某团队/my-project.git这里有个细节如果你只需要看代码并不打算改可以加上--depth 1做浅克隆只拉最新一次提交速度会快很多适合大仓库。git clone --depth 1 gitgithub.com:某团队/my-project.git我在实际项目里经常遇到有人纠结该用 init 还是 clone。判断标准很简单本地目录本来就是空的新项目用 init要接手别人的已有仓库用 clone。还有一点某公司在用自己的代码托管平台时建议先在平台上建空仓库再在本地 init 后手动关联远程地址这样分支保护和权限配置都在平台上控制得更顺手。2.2 看清当下状态status 和 diff如果只允许我教别人两个 git 指令我会先教git status和git diff。因为绝大多数我好像把仓库搞乱了的瞬间你需要的不是猛操作而是先搞清楚当前到底处于什么状态。git statusstatus会告诉你三件事哪个分支、相比远程领先或落后几个提交、工作区里有哪些改动。它不会告诉你改了什么内容只想看具体改动时用git diff。git diff默认情况下git diff显示的是工作区相对暂存区的差异也就是你后来又改了哪些还没 add 的东西。想看已经暂存但尚未提交的内容用git diff --staged有些版本记成git diff --cached两者等价。git diff --staged我自己的习惯是每准备提交一次改动前先跑一遍git diff --staged确认准备打包的内容正是自己脑子里想的那批。很多粗心提交都是因为少看这一步把调试日志、临时文件甚至敏感信息一起提交上去了。2.3 把改动提交干净add 与 commit 的细节git add是把文件从工作区放进暂存区的动作。最简单粗暴的是全加git add .我更推荐按文件加或者用交互式分段加。按文件加能避免把无关改动卷进来git add src/utils/format.js tests/format.test.js交互式分段暂时不用展开记住有个git add -p的指令就可以了它会逐个 hunk 问你要不要把这个片段加进去适合一个文件里既有功能改动又有格式化改动的情况。这个功能我几乎每天用属于极易上手的实用技巧。git commit是真正生成提交记录的指令。务必记住写清楚提交信息不要偷懒只写git commit -m update。好的提交信息应该让别人以及三个月后的你一眼看懂这次提交的意图。git commit -m refactor: 抽取日期格式化逻辑到独立工具函数如果提交后发现漏了一个文件或者提交信息写错了可以用--amend修正上一次提交而不用生成一条新的提交记录git add src/utils/format.js git commit --amend -m refactor: 抽取日期格式化逻辑到独立工具函数并补充单元测试需要特别提醒--amend会改写提交记录如果这条提交已经 push 到远程并且其他同事基于它做了工作尽量不要用 amend避免给别人带来一堆同步麻烦。3. 分支与合并把开发路径理顺3.1 新建分支与切换分支分支是 git 最被称赞的设计也是很多团队协作跑起来的基础。理解分支时可以把它想象成两个互不干扰的平行世界你在 main 分支上维护稳定版本在 feature 分支上开发新功能两边互不打扰。查看本地分支git branch新建并切换分支git switch -c feature/login很多旧教程让你用git checkout -b feature/login功能一样。不过我现在更推荐git switch因为checkout承担的职责太杂容易把概念搞混。switch只负责切换分支语义更干净。分支命名也有讲究。我见过有人在分支上写test、bugfix1、asdf过两周根本不知道这个分支对应什么问题。建议用一套容易理解的分词方式比如feature/用户登录、fix/修复支付超时、chore/升级依赖版本。分隔符用斜杠还可以在平台里自动归组查看分支列表时非常清晰。3.2 合并分支前的自我检查功能开发完要把 feature 分支合回 main常用的是git merge。git switch main git pull origin main git merge feature/login这里有一个非常容易被忽略的脏坑合并前一定要先同步远程的最新 main 分支。如果你在本地合并前 main 已经落后了合并出来的结果很可能带着冲突甚至把别人已经删掉的代码又加回来。所以我的标准流程是先切到 mainpull 一下确认本地 main 和远程一致再执行 merge。合并完成后feature 分支通常就被删除git branch -d feature/login如果 feature 分支上的某些提交还没合并就删除会提示你删不掉这是 git 在保护你。想强行删除可以用-D但千万别养成就-D的习惯误删分支的事我见过太多次。3.3 rebase 的正确使用姿势git rebase是另一个整理提交历史的工具。它和 merge 的结果很像但思路完全不同。merge 是把两条路径汇合到一点因此会产生一个额外的合并提交节点rebase 是把当前分支上的提交摘下来重新排列到目标分支的最新提交后面像是把笔记本从一叠乱纸中抽出来重新整理最后呈现出一条直线的历史记录。git switch feature/login git rebase main用 rebase 的好处是提交历史非常干净每条提交从早到晚排成一条直线做 code review 时很舒服。但代价是它改写了提交记录的时间线逻辑。这里有一个铁律绝不 rebase 一个已经推送到远程、并且别人也基于它开发过的分支。rebase 后的提交哈希全都变了别人再 pull 的时候会遇到一堆莫名其妙的冲突甚至可能导致好几份错乱副本。我刚学 rebase 时也犯过这个错把两个同事的工作搅成一团乱麻最后被迫一个个 reflog 手工找提交。那一次之后我给自己立了个规矩只对本地的、还没推送的分支做 rebase已经推上去的提交用 merge 处理。4. 远程协作与发布4.1 远程仓库的基本管理本地仓库和远程仓库之间的联系通过一个叫remote的配置来管理。查看当前关联了哪些远程仓库git remote -v新增远程仓库git remote add origin gitgithub.com:某团队/my-project.git如果项目换了一个托管平台或者原地址失效了可以修改关联地址git remote set-url origin gitgithub.com:某团队/my-project.git还有一种常见情况本地初始化项目后想把项目推到平台需要先关联远程再 push。很多新人一上来就 push结果报no remote configured错误。完整的初始提交流程大致是git remote add origin 仓库地址 git branch -M main git push -u origin maingit branch -M main是把当前分支重命名为 main-u是建立上游跟踪关系这样之后直接输入git push就能推送到对应的远程分支不需要每次带上远程分支名。4.2 push、pull、fetch 三兄弟别搞混远程同步相关的三个指令很容易混。我做了个表格帮助记忆指令动作是否合并到本地典型场景git push本地提交推送到远程否把功能推上远程分支git fetch从远程拉取最新提交信息否先看一眼远程都有什么新提交暂时不动本地代码git pull从远程拉取并合并是同步远程最新代码到本地分支很多人只知道git pull却忽略了fetch。其实fetch非常有用你先拉取远程状态然后用git diff origin/main看看远程改了哪些内容确认没问题再决定merge还是rebase。我一般会在大规模远程变动的场景下用 fetch 手动合并避免 pull 自动合并带来意外冲突。git pull默认行为是 fetch merge会产生一个合并提交。如果想保持历史线更干净可以用git pull --rebase本质上是先把本地未推送的提交暂存起来拉取远程新提交再把你自己的提交重放到最新位置。注意这条指令同样遵守不要对已推送分支随意使用的原则对个人功能分支倒是很合适。4.3 给版本打标签当一个版本准备发布时我习惯用 tag 标记一个里程碑提交。tag 相当于给提交打一个可读的名字例如 v1.0.0、v1.2.3。轻量标签直接创建git tag v1.0.0附注标签会包含打标签人、时间、注释等元信息更适合发布版本git tag -a v1.0.0 -m 正式发布 1.0.0 版本查看所有标签git tag -l注意tag 不会通过 push 自动推到远程需要显式推送git push origin v1.0.0或者一次性推送所有标签git push --tags我在参与某个前端项目时项目组就吃过没打 tag 的亏发布后线上出问题翻代码库找不到当时上线的是哪个提交只能靠时间推断费了很大劲。从那以后我每次发布必然打带注释的 tag方便后续回滚定位。5. 撤销与回滚把后悔药吃明白5.1 还没提交时工作区改动怎么还原工作中最典型的场景是改了一堆代码突然发现自己走错了方向想把文件恢复到上一次提交的状态。这时用git checkout -- src/utils/format.js这条指令的含义是用暂存区或上一次提交的内容覆盖工作区文件。如果改动的文件已经git add进了暂存区想撤销暂存状态但保留工作区改动git restore --staged src/utils/format.js新版 git 更推荐用git restore系列指令语义更清晰。checkout身兼职责太多容易让人困惑它既能切分支又能还原文件我建议普通文件还原用restore分支切换用switch。5.2 已经提交但没推送reset 的三种模式一旦你生成了一条提交记录要撤销或重写这条记录就用git reset。它有三种模式也是很多新人最容易搞混的地方。git reset --soft HEAD~1--soft只是把 HEAD 指针回移到上一个提交工作区和暂存区都不动。相当于把提交拿下来但你自己改的内容全部还保留着而且已经 add 好了。适合提交信息写错了想重新提交的场景。git reset --mixed HEAD~1--mixed是默认模式它会撤销提交同时把暂存区清空但工作区改动保留。适合提交后发现想拆成多个提交的场景。git reset --hard HEAD~1--hard是最危险的一个它不仅移动 HEAD还重置暂存区和工作区。执行完之后工作区里所有改动直接消失。我在带新人时反复强调如果没搞懂三个模式的区别宁可先用git log查清楚再动也不要拿--hard乱试。--hard一旦执行很多改动就再也找不回来了除非借助 reflog 恢复。5.3 已经推送revert 才是正道如果你已经执行过 push别人也可能基于这份代码做了工作这时候再用 reset 就会造成远程和本地历史不一致非常难处理。正确的做法是git revert。git revert 8f3a30arevert不是抹掉历史而是生成一条新的提交把之前的改动反向执行一遍。这样旧提交依然保留在历史里但它的效果被新提交抵销了远程协作不会出现历史重写问题。这个方法也适用于已经发布的版本出了问题我想快速回滚到上一版的场景。处理方式是在 main 分支上直接 revert 掉那个有问题的提交然后 push线上代码就恢复正常了。历史记录里保留了一次撤销了某某提交的记录对后续排查很有帮助。5.4 stash 临时保存手头工作还有一种常见情况你在 feature 分支改了代码但还没改完突然要切到另一个分支去修一个紧急 bug。如果直接切换工作区改动会被带回分支或者被阻塞在切换操作中。这时用git stash把未提交的改动存起来把工作区恢复干净切分支改 bug然后再回来取出改动。git stash git switch main git switch feature/login git stash popgit stash相当于把所有未提交的改动放进一个临时小仓库。git stash list可以查看有哪些暂存记录。如果要应用某一条而不是最新的一条git stash apply stash{1}stash是我实际使用频率非常高的指令尤其适合那种代码写到一半老板突然让你去查 bug的场景。需要注意stash不会保存未跟踪的新文件除非显式加-ugit stash -u6. 常见问题与排查技巧实录6.1 detached HEAD 状态是怎么回事git status里出现detached HEAD时很多新人有点慌这感觉像是头掉了。其实它表示 HEAD 没有指向任何一个分支而是直接指向某个具体的提交。通常是因为你做过类似git checkout commit的操作。在这个状态下你提交的代码会临时处在一个悬空的提交上没有任何分支引用它。如果此时再切回分支这些提交可能被 git 当作不可达提交清理掉。解决办法很简单如果只是想看看旧代码看完切换回分支即可。如果发现旧提交上有一段值得保留的工作立刻创建一个新分支让分支指向当前悬空前后的位置git switch -c temp-branch我见过的典型误操作是想查看某次历史版本用了git checkout 9fce2a1新同学改了半天发现保存不了最后被强制切走导致工作丢失。遇到这种情况先冷静用 reflog 还能找回来一些详情见 6.3。6.2 冲突解决的完整流程冲突是 git 使用中最绕不过去的一道坎。它本质上是两个分支修改了同一处内容git 不知道应该保留哪一份。一般在 merge 或 rebase 过程中出现。冲突发生后被标记为冲突的文件里会出现类似这样的内容 HEAD 你当前分支的代码 另一个分支的代码 feature/login处理流程并不复杂直接打开冲突文件把、、这些标记删掉。仔细阅读两边代码决定保留哪一份或者手动把两边逻辑融合起来。保存文件后执行git add把解决后的文件加入暂存区。继续执行 merge 的收尾动作比如git merge --continue或git commit。我在实际项目中处理冲突的经验是不要试图快刀斩乱麻一定要理解两边代码的意图。有一次同事为了快点解决冲突直接选了某一侧的版本结果把对方刚封装的接口调用直接删掉了合上去后编译失败大家一起排查了很久。真正高效的冲突解决是花时间看上下文、跑测试确认结果是对的再提交。6.3 误删分支、误改文件后的恢复思路误操作后最有效的工具是git reflog。它记录了 HEAD 曾经指向过的每一个位置包括被 reset、checkout、merge 等操作改写之前的状态。git reflog输出形如8f3a30a HEAD{0}: reset: moving to HEAD~1 5c4e2d1 HEAD{1}: commit: fix: 修复登录超时问题 1b9d0cf HEAD{2}: checkout: moving from main to feature/login如果你误删了一个分支通过 reflog 找到分支最后一次指向的提交哈希然后重新创建分支git branch feature/login 5c4e2d1如果你误执行了git reset --hardreflog 里还能看到被 reset 前的提交用同样方式把 HEAD 指回去就能找回丢失的工作。这个方法我至少救过三四个人的代码知道的人不是很多。需要注意reflog 是本地仓库的日志不能通过克隆获取。而且它包含的信息也会随着仓库的 gc 清理而消失。所以出现误操作后越早恢复成功率越高不要等几天再来处理。6.4 几条最实用的排查命令除了上面讲过的git status、git diff、git reflog我再分享几条调试和排查时特别有用的指令。查看提交历史用git log --oneline --graph --all。--oneline折叠成简洁单行--graph画出分支走向--all把本地和远程所有分支都展示出来。这个组合是我平时最常见的视图git log --oneline --graph --all只看某个人在某段时间提交了哪些内容git log --author某开发者 --since2024-01-01 --until2024-06-30 --oneline查看某次提交具体改了哪些文件git show 8f3a30a --stat还有一个很浅但很多人没注意的git status会直接告诉你下一步该执行什么指令。比如提示 use git restore to discard changes in working directory如果你实在不知道接下来该干嘛就跟着 git 的提示走基本不会出错。我在团队里常说git 排查指令不求多但求用熟。上面这些足够覆盖日常工作中九成以上的问题。遇到没见过的报错先看提示信息再思考自己刚才到底对哪个区域做了操作比顺手乱敲几条指令高效得多。我个人在实际操作中的体会是git 指令记住多少不重要真正重要的是每次敲指令前脑子里要清楚这条指令作用在哪个区域、会改变什么。理解了工作区、暂存区、本地仓库、远程仓库这四层结构你就不会再怕git 又搞出什么奇怪状态。最后再分享一个很小但很受益的习惯每天结束工作前跑一遍git status加git log --oneline -5花不到一分钟却能让你对项目变化保持清晰的掌控感。这套方法我在多个项目里用下来几乎没再为版本管理的事焦虑过。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →