1. 先把Git的底层逻辑盘清楚后面全是顺水推舟1.1 版本控制不是备份是后悔药加平行宇宙我见过太多人刚接触Git时把文件夹复制一份改成最终版过两天再复制一份改成最终版2电脑里堆满了最终版_final_v3_再也不改这种名字。这不是版本控制这只是备份。备份解决的是文件丢了怎么办版本控制解决的是我想回到过去、我想并行开发、我想知道每一行是谁写的为什么这么写。Git的价值用一句话概括它是一个可以任意回溯的时间线同时提供多条互不干扰的平行时间线。时间线就是提交历史平行时间线就是分支。如果你能先建立这个认知后面命令学起来会快很多。Git是分布式版本控制系统和早期的集中式系统比如SVN有本质区别。SVN的仓库放在一台中心服务器上你本地只有一份工作副本想查看历史、创建分支都得联网问服务器要。Git把整个仓库完整地克隆到本地版本历史、分支、标签全在本地绝大多数操作离线就能完成只有在push和pull的时候才需要联网。这个差异带来的体验是颠覆性的你在高铁上、飞机上、网络断开时依然可以正常提交代码、切换分支、查看历史等网络恢复再把变更推上去。分布式还有一个隐藏优势是容灾本地任何一个人的仓库都是一份完整备份服务器挂了也丢不了代码。1.2 三个区工作区、暂存区、版本库Git最劝退新手的就是暂存区这个概念。你用IDE添加代码、修改文件保存之后你以为Git已经记住了其实Git只知道你改了哪些文件但不知道你准备把哪些改动算作一次提交。Git把一个项目在本地分成三个区域工作区你肉眼可见、正在编辑的那些文件就是你磁盘上的实际内容。暂存区Index/Stage一个待提交清单你通过git add把选中的改动放进这里。版本库Repository已经提交并永久保存的历史快照由git commit写入。一个新手最容易犯的错误是以为git add .之后万事大吉随手git commit -m 改了一堆东西结果提交里塞满了调试日志、临时文件、甚至.env密钥。正确的姿势是git add只把你真正想纳入本次提交的文件放进去其他改动留在工作区继续改。对应的差异查看也有规律git diff看工作区和暂存区之间的差异git diff --staged看暂存区和版本库之间的差异git diff HEAD则把工作区所有未提交的改动全部显示出来。这个规律记住之后排查问题速度会快很多。另外要理解HEAD这个指针。HEAD永远指向当前分支的最新一次提交可以把它理解为你现在站在时间线的哪个位置。很多命令带不带HEAD效果差别很大后面讲撤回的时候会反复用到。2. 安装与初始化这些细节决定你后续会不会踩坑2.1 安装Git时最容易被忽略的三个选项网上很多Git安装教程都在讲下一步下一步但恰恰是几个默认选项坑了人。以Windows为例安装包里有三个选项值得认真对待第一是PATH环境变量。一定要选Git from the command line and also from 3rd-party software这样你在CMD、PowerShell、IDE终端里都能直接敲git。如果选成仅Git Bash使用后面大概率会遇到找不到git命令的报错。第二是换行符转换。Windows用CRLFLinux/macOS用LFGit默认会在检出和提交时自动转换。我的建议是选Checkout as-is, commit as-is也就是不自动转换。因为自动转换是所有换行符幽灵bug的根源明明只改了一行git diff却显示整个文件都变了。团队约定好统一用LF提交进仓库的代码就不会有歧义。第三是默认分支名。新版Git安装时可以设置初始分支名我建议直接设成main。现在主流托管平台上的新仓库默认分支大多是main本地仓库保持一致可以少一些不必要的困惑。macOS上安装Git简单brew install git一行命令搞定但要注意Xcode自带的Git版本通常偏旧不嫌麻烦就先检查git --version。Linux用户用包管理器装就行Ubuntu/Debian是apt install gitCentOS/RHEL是yum install git。2.2 初始化必须做的两件事user.name和user.emailGit装好之后第一件事不是急着建仓库而是设置身份信息。很多人忽略这一步等第一次提交完毕才发现提交者姓名是unknown或者一串随机字符再改就麻烦了。# 全局配置针对当前用户所有仓库生效 git config --global user.name 你的名字 git config --global user.email youexample.com # 项目级配置只对当前仓库生效优先级高于全局 git config user.name 团队内显示的名字 git config user.email 团队内使用的邮箱这里有个反直觉的点user.email并不要求填你注册GitHub或Gitee的邮箱它只是提交历史上的一串身份标记。只要团队成员能通过它找到你本人就行。很多公司内部会用统一规则比如工号或内部邮箱这种就按团队规范设。如果某次提交发现身份设置错了可以改完之后用git commit --amend --reset-author修正最近一次提交的作者信息。还没推送到远端的话这个操作是安全的。2.3 SSH密钥配置以Gitee为例GitHub同理日常开发里clone和push的认证方式最推荐SSH。走HTTPS每次还要输账号密码或者依赖credential管理器经常出现莫名其妙失效的问题。SSH密钥只需要配置一次之后所有仓库都免密操作。Windows和macOS用户打开终端Windows推荐用Git BashLinux用户直接开shell执行# 生成ed25519类型的密钥替换成你自己的邮箱 ssh-keygen -t ed25519 -C youexample.com一路回车就行默认保存在~/.ssh/id_ed25519。如果想给密钥加个密码保护会要求你输入两次密码这个看个人习惯加密码更安全但每次用都要输我图省事通常不设。然后查看公钥内容cat ~/.ssh/id_ed25519.pub把输出的一整行复制下来。登录Gitee打开设置里的SSH公钥粘贴保存。GitHub的路径是Settings - SSH and GPG keys - New SSH key逻辑一模一样。验证是否配好ssh -T gitgitee.com第一次会提示确认指纹输入yes回车看到Hi xxx! Youve successfully authenticated就说明成功了。GitHub对应的验证命令是ssh -T gitgithub.com。2.4 一份可以直接抄的.gitconfig配置项散落各处容易记不住我贴一份我自己在用的最小配置按照需要增删[user] name yourname email youexample.com [init] defaultBranch main [core] editor code --wait [alias] st status -sb co checkout ci commit br branch lg log --graph --oneline --decorate --all [color] ui truecore.editor设置成code --wait之后执行git commit不加-m时会用VS Code打开提交信息编辑窗口比在终端里写多行提交信息舒服得多。没有VS Code就换成vim或者nano也可以直接一直用-m。3. 日常开发核心循环克隆、分支、提交、推送3.1 clone之后第一件事拿到一个项目仓库大多数人的第一个命令是git clone gitgitee.com:yourname/project.git然后马上开始改代码。这里我建议多花30秒做两件事看一眼状态和分支。# 进入项目目录后 git status git branch -agit status确认工作区是干净的git branch -a列出本地和远程全部分支你能看到当前在哪个分支、远端还有哪些分支没有拉到本地。如果远端有个feature/payment分支你想切过去看直接git checkout -b feature/payment origin/feature/payment这条命令的意思是以远端分支origin/feature/payment为起点创建一个本地分支feature/payment并切换过去。这是团队协作里最常用的操作之一比直接用git checkout feature/payment更明确——后者有歧义Git可能不知道你要建新分支还是切旧分支。3.2 分支永远不嫌多从checkout到switch很多团队新人分支管理混乱根源在于不敢开分支所有改动都堆在main上最后以大量冲突收场。正确的习惯是每个任务开一个分支任务做完合并删除分支生命周期短、职责单一。创建并切换分支新版Git推荐用git switch -c feature/user-login传统的写法是git checkout -b feature/user-login两者效果一样switch语义更清晰专门用来做分支切换checkout还要兼顾文件恢复功能一命令多义新手容易绕晕。切换普通分支就git switch main回到上一个分支用git switch -类似shell里的cd -。分支命名我见过各种风格目前团队里比较通用的是type/名称式feature/xxx是新功能fix/xxx是修bugchore/xxx是重构或杂务。这样一看分支名就能猜到干的是什么活。3.3 提交粒度与提交信息的质量提交是整个Git工作流里频率最高的动作但多数人提交得很随意。我见过的典型场面是一个提交里改了数据库表结构、修了一个拼写错误、加了两个新接口、顺带格式化了一整个文件。等上线出了问题想回退某个功能却发现所有改动缠在一起根本退不干净。正确的提交粒度应该是一个提交只做一件原子的事情。修复一个bug、添加一个小功能、调整一处配置各自独立提交。这样git log读起来像一篇清晰的变更日志出问题的时候用git bisect二分查找也能精确定位到是哪次提交引入的。提交信息同样重要。我推荐最简单的规范第一行是类型加简述例如fix: 修复登录按钮在Safari下不可点击的问题不超过50个字符。有需要解释的部分空一行再写详细说明比如改动原因、影响范围、测试方式。这里有个实用技巧提交时用git commit -vGit 会自动把暂存区的内容以注释形式显示在提交信息下方你看着差异就能确认我要提交的确实是这些改动而不是稀里糊涂把不该提交的文件一起打进去。3.4 pull、fetch、push背后的逻辑很多新手分不清pull和fetch以为都是把远端代码拉下来。其实git pull是两步动作的合并先git fetch把远端分支的最新提交下载到本地再git merge默认策略把远端分支合并到你当前分支。fetch只下载不合并你的工作区完全不受影响。实际开发中我建议遵循一个原则push之前先pull保持工作基于最新代码。直接git pull的问题在于如果你的功能分支和远端分支各自产生提交Git会生成一个merge commit历史变乱。如果你的分支就是自己的专属分支更推荐用git pull --rebase它会把你在本地新增的提交顶到远端最新提交的后面历史是一条直线。push的时候第一次往远端推新分支要加-u参数git push -u origin feature/user-login-u的作用是建立当前本地分支和远端同名分支的追踪关系之后在此分支上直接敲git push或git pull就行不用再写完整命令。4. 改错与历史修正commit --amend、reset、revert的使用边界4.1 commit --amend的正确姿势与禁忌commit --amend是热搜词里的常客也是很多人用错频率最高的命令它的功能翻译过来是修改最近一次提交。典型使用场景有三个第一个是提交信息写错了比如发现少了个空格、类型名写错直接改正git commit --amend -m fix: 正确的提交信息第二个是漏提交了文件刚提交完就发现忘了加一个配置文件git add config.yml git commit --amend --no-edit--no-edit表示沿用原有提交信息不打开编辑器适合补充提交遗漏内容不想改动消息的情况。第三个是提交完发现自己改错了一个小地方本地改好之后可以直接合入上一次提交保持历史的整洁而不是产生一个fix fix fix的脏提交。但这里有一条铁律不要amend已经推送到共享分支的提交。原因是--amend并不是修改了原提交而是创建了一个全新的提交替换掉旧提交只是新的提交信息等内容看起来像原提交的修正版。一旦本地历史变化下次push时Git会发现本地和远端历史不一致要么被拒绝要么团队其他人git pull时拉出一堆冲突分叉过程相当痛苦。4.2 reset三兄弟--soft、--mixed、--hardgit reset是回滚本地提交的利器它有三个模式很多人只记得一个--hard出问题就后悔莫及。--soft只移动HEAD指针暂存区和工作区全部保留。意思是撤销提交但保留所有改动在暂存区可以马上重新提交。--mixed默认移动HEAD指针并清空暂存区工作区保持不动。意思是撤销提交改动回到工作区需要重新add。--hard移动HEAD指针暂存区和工作区全部重置。意思是彻底丢弃改动的文件内容也会恢复成指定提交时的状态。举个具体例子。你发现最近3次提交里有问题想把这3个提交全部撤销保留代码改动重新整理# 假设当前在普通开发分支没有push到远端 git reset --soft HEAD~3这时3个提交内的所有改动会集中到暂存区你可以重新git add分组创建更合理的提交序列。HEAD~3的意思是当前提交之前第3个提交也就是往回退3个提交。--hard是我极其谨慎使用的模式。我见过不止一个同事执行git reset --hard HEAD之后发现一个重要文件忘了提交然后就再也没有了。如果你只是想扔掉未提交的改动在动手之前先想想是不是该用git stash把它保存起来。如果不小心已经执行了git reset --hard而且被丢弃的内容里有关键代码还有一个紧急救法只要你知道被丢弃代码的commit哈希比如刚从git log里看到过用git reset --hard 哈希还能找回。但如果你自己都不知道那个哈希基本就找不回来了。4.3 revert是给公开历史用的刹车revert和reset表面上都是撤销但机制完全不同reset是把时间线倒回去revert是新增一个反向提交把之前的变更抵消掉。这个区别对公共分支特别重要。比如main分支刚合并了一个feature你发现它引入了问题想撤掉。如果直接git reset --hard 上一个提交再git push -f第一强推会覆盖远端历史团队其他人本地如果拉过这个分支下次同步立刻冲突第二公共历史被改写这在很多团队里是不被允许的。正确的做法是git revert 出问题的commit哈希Git会生成一个新提交取消目标提交的改动然后正常push就行。所有人的历史都是一条向前延伸的线没有任何人需要特殊处理。虽然日志里能同时看到添加了某功能和撤销了某功能两个提交看起来有点冗余但这是保证公共历史可追溯、全员同步安全的代价。4.4 用log快速定位你想改的那个提交无论是reset、revert还是review第一步永远是找准commit哈希。只会用git log默认可太慢了一个稍微大一点的项目屏幕滚动几页全是密密麻麻的提交时间看得头晕。日常我基本只用这几条log命令# 最常用的图形化单行显示引用一眼看全貌 git log --oneline --graph --decorate --all # 只查某个文件的历史 git log --oneline -- path/to/file # 查看某个提交的具体改动 git show commit哈希 # 全文搜索提交信息 git log --grep登录--oneline让每条提交只占一行--graph用符号画出分支合并图--decorate显示分支和标签指针--all把本地和远端所有引用都展示出来。这套组合基本是标配不少人的git aliases里那个lg就是这个。定位到目标提交之后想查看这个文件在某个提交时的内容git show commit哈希:path/to/file这个命令在代码被改坏了我想看看改之前长什么样的场景下特别好用。5. 多人协作的硬仗合并、冲突与提交规范5.1 merge与rebase的取舍团队协作中两个分支之间的合并方式主要就是merge和rebase两者的区别直接决定你的提交历史是一团乱线还是一条直线。git merge feature/user-login会把 feature 分支并入当前分支并生成一个专门的合并提交merge commit。它的好处是保留了真实的开发过程谁在哪个分支干了什么一目了然缺点是提交历史会出现分叉再叠加多个并行开发分支之后git log --graph看起来像一张蜘蛛网。git rebase main的做法是把你当前分支上独有的那些提交一个一个摘下来重新放到main分支的最新提交后面像把一条插线板重新捋直。好处是历史是一条干净的线review代码时顺着提交一个个看过去很流畅代价是重写了提交历史每个被rebase的提交哈希都会变化所以凡是已经push到共享远端、其他人正在用的分支绝对不要rebase。我个人的实践建议是更新你自己未推送的分支时用rebase比如git pull --rebase或git rebase main保持本地历史干净。合并已经review完的功能分支到公共分支时用merge至少保留分支合并的完整性便于追溯。还有一种更细的做法在合并前用git merge --rebase或git rebase把功能分支变整洁再merge回main这就是rebase feature, merge main策略很多团队都在用兼顾了历史线性和合并完整性。5.2 冲突为什么会发生一个具体的例子冲突不是Bug它是Git无法替你决定时的正常表现。只要两个分支修改了同一块代码Git不知道保留谁的就会把决定权交给你。举个例子你和同事同时开发登录功能他在feature/login分支改了login.js第20行的validateForm函数你在feature/oauth分支也改了同一个文件同一行。你们都已经推送了自己的分支等到合并时Git检查两边差异发现同一行内容都不一样于是报出冲突。冲突发生后进入冲突状态的文件会包含特殊标记 HEAD const timeout 5000; const timeout 8000; feature/oauth HEAD到之间是当前分支HEAD的内容到 feature/oauth之间是对方分支的内容。你需要做的不是直接选一边而是想清楚这两个逻辑到底哪个对如果业务上需要8秒超时就删掉上面那段保留8秒如果两边要共存也可能把两段都保留成不同变量。不管怎么处理记住最后要删掉三行标记符号。5.3 一次冲突解决的完整链路很多新手遇到冲突就慌是因为不知道接下来到底该干嘛。我用最常见的场景走一遍完整流程。假设你在feature/payment分支git pull时远端main已经有了新提交自动合并失败$ git pull Auto-merging payment.js CONFLICT (content): Merge conflict in payment.js Automatic merge failed; fix conflicts and then commit the result.第一步先看当前状态git status输出会明确告诉你哪些文件有冲突处于Unmerged paths状态。第二步打开冲突文件搜索冲突标记逐个处理。处理完的标记要全部删除。第三步把处理好的文件标记为冲突已解决git add payment.js这一步特别重要git只有看到了git add才认为这个文件的冲突被解决了。如果整个项目一共有3个冲突文件就要把3个文件都处理好并add。第四步git pull的合并冲突解决后不要直接git commit写一句解决冲突而是用git commitGit会弹出一个预填好的合并提交信息里面通常会写Merge remote-tracking branch origin/main into feature/payment这种信息保留即可可以直接保存。如果是git pull --rebase或git rebase过程中发生的冲突第四步有所不同要执行git rebase --continue冲突解决后不需要手动提交rebase会接着把剩余提交一个个完成。一个特别坑的细节冲突解决完测试了半天发现思路不对想放弃重来怎么回到冲突发生前的状态合并冲突是git merge --abortrebase冲突是git rebase --abort。如果已经git add了好几个文件abort会一并还原所以不用怕add完了就回不去了。5.4 保护分支与提交规范团队协作光有命令不够还得有规范。绝大多数团队会用保护分支机制main/master分支被设置为禁止直接push所有修改必须通过Pull RequestPR或Merge RequestMR流程合入。这个机制的价值不只是代码审查更重要的是让每一个进入主干分支的提交都经过验证、有迹可循。新功能开分支推到远端写清楚PR描述关联对应任务编号CI自动跑测试和lint至少一个人review通过之后才能合并。作为开发者你需要掌握的日常操作就是把本地分支推到远端并发起PRgit push -u origin feature/payment然后去Gitee或GitHub页面上创建PR/MR选择目标分支为main填写变更说明。团队规范通常在PR描述里要求写改了什么、为什么改、测试方法、影响范围。这也促使你自己在提交前把代码整理干净。我见过很多团队刚迁到Git时冲突频发主要原因不是Git不好用而是没有养成小步提交、频繁同步的习惯。一个小改动拖了两周才从分支推出去等要合并时main已经天翻地覆不冲突才怪。规范的做法是功能拆细、分支开小、至少每天同步一次远端遇到冲突及时解决不要拖。6. 高频场景速查日志排查、临时暂存与效率别名6.1 查日志、找作者、看差异写代码三分之一的时间在改别人的历史代码这时候Git的排查能力就是救命稻草。想知道某个文件里某行代码是谁写的、来自哪次提交git blame -L 10,20 src/login.js输出会列出第10到20行每一行对应的commit哈希、作者、提交时间。这个命令在定位这行神奇的代码是哪个大哥加的时极其有用review和追责都靠它。想知道某次提交到底改了哪些文件、每个文件改了多少行git show --stat commit哈希想看某个文件两个版本之间的差异git diff 提交A 提交B -- src/login.js如果差异太大难以阅读可以只关注上下文用git diff --stat 提交A 提交B先看文件层面改了哪些再针对性地看具体文件比直接扎进几万行的diff里高效得多。6.2 stash临时放下手头的工作开发中经常遇到这种场面你在feature/login分支改了一半代码突然线上有紧急bug要立刻切到fix/hotfix分支处理。如果工作区有未提交改动直接git switch会被Git拦住因为切换分支可能造成冲突或丢失工作。扔掉改动重新写不现实这种情况就用git stash把现场暂时封存# 保存当前所有未提交的改动 git stash push -m 登录功能未完成表单校验写到一半 # 查看暂存的列表 git stash list # 切到其他分支干活改完之后切回来恢复刚才的改动 git stash popstash pop会取出列表中最新的那一条并恢复恢复时如果有冲突解决方式和普通合并冲突一样。如果你只是想看看某条stash里有什么用git stash show -p stash{1}。这里有个实用原则stash适合短期临时保存不适合长期保存工作成果。所有stash内容都是脆弱的建议每天结束前把还在stash里的改动要么提交到分支、要么创建新分支专门保存防止误删或忘记。6.3 高效的别名配置Git命令大多是长单词天天敲其实很手累。我用了一年之后给自己配了一套别名基本都是高频命令的缩写[alias] st status -sb co checkout sw switch cb switch -c ci commit br branch lg log --graph --oneline --decorate --all df diff dfs diff --staged pushf push --force-with-lease undo reset --soft HEAD~1其中pushf我特意用的--force-with-lease而不是--force这两个的区别值得单独讲。git push --force是无脑覆盖远端git push --force-with-lease会先检查远端在你上次fetch之后有没有新的提交如果远端已经变了就拒绝强推防止误伤别人的更新。日常需要强推时永远优先用带--lease的版本。undo reset --soft HEAD~1是我最常用的别名意思是撤销最近一次提交但把所有改动保留在暂存区相当于给commit完马上发现漏了东西的组合拳提供快捷键。6.4 与IDE和CI配合的常见细节现在大多数人不会只在终端里操作GitIDE的图形化界面和CI持续集成系统也深度依赖Git。有几个细节很多人第一次遇到时都会懵。IDE集成方面JetBrains全家桶和VS Code集成的Git功能都很完善但初学者容易在IDE里点错按钮比如Discard All直接把所有改动扔了。IDE里看似方便的回滚操作底层往往对应git clean或git checkout .是不可恢复的。我的建议是对命令还不熟的时候IDE只用来查看diff和提交涉及回滚、强推、reset这类危险操作回到终端手动敲命令。CI系统里最常见的问题是分离头指针。很多CI工具用git checkout commit哈希来拉取某个特定提交这会让仓库处于分离头指针状态此时所在分支指向一个悬空提交任何新提交都不会在任何分支上。CI里跑测试、构建没问题但如果在CI里执行git push一定会踩坑。所以CI脚本里指向具体提交时尽量避免依赖当前分支的概念。还有一个高频细节.gitignore文件最好在项目初始化时就写好。常见要忽略的包括node_modules/、dist/、.env、各种IDE配置文件、操作系统的.DS_Store。否则第一次git add .就把依赖包、构建产物全提交进去了后面再清理极其麻烦。如果你曾经不小心提交过不该提交的文件别指望靠.gitignore自动移除它只对未跟踪文件生效已经进仓库的文件要手动git rm --cached file从版本库中移除但保留本地文件。