
1. 初学Git最容易卡住的三个认知误区写这篇分享之前我回想了一下这些年带新人的经历。几乎每个人学Git前两周都会陷入同样的三个误区而且这三个误区不解决后面写再多命令都是白搭。所以我不打算上来就甩命令清单先把这些认知掰正了你后面学起来会顺很多。1.1 误区一把Git当成一个网盘很多人第一次接触Git心里想的是这不就是个带历史版本的云盘吗我把文件传上去需要的时候再下载下来。这么理解带来的直接后果是你会下意识地把所有文件一股脑塞进去然后发现仓库越来越乱历史记录惨不忍睹。Git和网盘的本质区别在于网盘同步的是文件内容本身而Git管理的是文件内容的变化过程。每一次commit不是上传了一个新版本而是记录了一次从上一个状态到当前状态的变化。Git的底层存储其实是一串环环相扣的快照每个快照都会记录父提交是谁这样整条历史就串成了一条链。理解了这个你就能明白为什么Git被称为分布式版本控制系统。每个人的本地仓库都包含了完整的提交历史不需要联网也能提交、也能查看历史、也能创建分支。网盘做不到这一点断网就什么都干不了。另外还有一个实际影响Git仓库里的对象是用内容寻址的方式存储的文件内容相同的部分会被复用不会重复存储。所以不用太担心提交了很多次会让仓库体积暴增——当然二进制大文件仍然是个例外这个后面单独说。1.2 误区二命令行和图形化工具必须二选一我发现新人特别容易走极端要么坚持只用IDEA的图形界面就够了命令太吓人要么反过来觉得用图形界面不算真本事必须敲命令。这两种心态都不太健康。实际情况是Git的命令行工具是能力最完整、最稳定的操作方式而图形化工具比如IntelliJ IDEA自带的Git插件、VS Code的Git面板是提升效率的辅助手段。二者是配合关系不是竞争关系。我的建议是先把常用的20个左右命令练熟确保你在没有图形界面的情况下也能完成日常操作然后再回到IDE里用图形界面做高频操作比如查看diff、暂存部分改动、解决冲突。因为你一旦理解了命令在做什么再看IDE的操作就不至于点按钮但不知道点了什么。这个理念会贯穿这篇博客的全文。我讲命令的时候会解释它背后的机制讲IDE操作的时候也会告诉你对应的是哪条命令。两条腿走路才走得稳。1.3 误区三分支合并一定会出冲突所以尽量少合并这个误区在团队协作的初期特别普遍。有人合并分支时遇到过几次冲突心里有了阴影于是不敢主动合并或者拖到不得不合并的时候才动手结果冲突更大。事实是冲突是正常的但它不等于灾难。绝大多数冲突的本质是两个人修改了同一文件的同一区域Git无法自动判断该保留谁的改动。这不是Git的缺陷而是Git诚实的一种表现——它宁可让你手动确认也不愿意自作主张地覆盖任何一方的劳动成果。更关键的是冲突的范围和频繁度是可以用工作习惯来控制的。比如每次提交的粒度小一点、分支存活的时间短一点、公共文件尽量少改动、pull的时候先看状态再决定怎么合。这些习惯养成了冲突的几率和解决成本都会大幅度下降。后面的分支管理章节我会专门演示一次完整的冲突解决过程包括那些容易让人慌神的、、标记长什么样以及每一步操作的意图。2. 环境准备安装、配置与SSH认证一条龙网上搜Git安装能找到一大堆教程但很多教程只讲了下载和下一步下一步忽略了安装之后必须做的验证和配置工作。我按自己的实际操作流程重新梳理一遍你可以照着走。2.1 下载安装不同平台的选择与验证Windows平台Git官方提供了Windows安装包git-scm.com下载exe文件后一路Next即可。但有几个选项值得注意在选择安装组件的页面建议勾选Git Bash Here和Git GUI Here——你会用到Git Bash。在调整PATH环境的页面推荐选择Git from the command line and also from 3rd-party software。这个选项会把git加入系统PATH这样你在CMD或PowerShell里也能直接用git命令。在换行符处理页面推荐选择Checkout Windows-style, commit Unix-style line endings。默认选项就是它。安装完成后打开Git Bash或CMD执行git --version能打印出版本号就说明安装成功。macOS平台mac上有三条常见路线直接下载官方dmg安装包运行后一路继续但可能需要右键打开绕过security限制。用Homebrew安装一行命令brew install git装Xcode Command Line Tools它自带一个版本的Gitxcode-select --install我个人推荐Homebrew方式因为后续升级方便brew upgrade git就能搞定。Linux平台各发行版的包管理器各不相同Ubuntu/Debian是sudo apt install gitCentOS/RHEL系是sudo yum install git装完后同样执行git --version验证。2.2 全局配置提交者身份必须做的第一件事很多人下载完Git就直接开始clone、commit然后发现提交记录里显示的不是自己的名字甚至是一串乱码。这是因为Git需要一个明确的提交者身份而这个身份来自全局配置。打开终端执行git config --global user.name 你的名字 git config --global user.email 你的邮箱注意两点--global表示该配置对所有仓库生效。如果某个特定项目需要单独身份可以在项目目录里去掉--global再配一次。邮箱最好和你的托管平台GitHub、GitLab、Gitea等绑定的邮箱保持一致这样提交才能正确关联到你的账号头像和贡献图。验证配置是否写好了git config --global --list它会打印出你的name和email。这里还推荐顺手设置几个常用项一个是默认分支名新版本Git默认是master很多人习惯用maingit config --global init.defaultBranch main一个是避免中文文件名在终端显示成八进制转义git config --global core.quotepath false还有一个是配置常用的文本编辑器。默认Git会用vi很多不熟悉vi的人会卡在commit编辑器里出不来建议改成自己熟悉的比如VS Codegit config --global core.editor code --wait2.3 SSH密钥一次配置从此免密推送用HTTPS方式clone和push需要每次都输密码虽然有些平台支持token代替密码但多一步操作就多一分烦琐。SSH密钥是更顺手的方案而且配置过程并不复杂。第一步检查本地是否已经有密钥ls ~/.ssh如果看到id_ed25519和id_ed25519.pub这两个文件说明之前生成过可以直接用。没有的话生成一对新的ssh-keygen -t ed25519 -C 你的邮箱执行后一路回车即可也可以用-f参数指定文件名非必要不需要。生成的公钥在id_ed25519.pub里私钥在id_ed25519里。私钥绝对不能泄露这相当于你家的钥匙。第二步把公钥内容复制出来cat ~/.ssh/id_ed25519.pub然后把输出的整行内容添加到托管平台的SSH Keys设置页面。第三步验证连接ssh -T gitgithub.com不同的托管平台对应的域名不一样GitHub是gitgithub.comGitLab是gitgitlab.comGitea通常是你自己部署的域名。如果第一次连接出现确认提示输入yes回车即可。看到类似Hi xxx! Youve successfully authenticated的提示就说明认证通过了。后面clone和push的时候把地址换成SSH格式形如gitgithub.com:user/repo.git就能全程免密操作。3. 日常开发必备的Git命令实战手册这一章是本文的重头戏。我不会把Git所有命令都罗列出来那样没有意义网上有完整的官方文档。我按一个开发者在真实项目中一周会用到什么这个标准来筛选并且每条命令都配上实际场景。3.1 工作区、暂存区、版本库的关系如果你不理解这三个概念Git命令就只能是死记硬背。用一句话概括它们的关系工作区你当前能看到的、正在编辑的文件目录。暂存区一个介于工作区和版本库之间的缓冲区是你决定这一次提交包含哪些改动的地方。版本库Git保存所有提交历史和快照的地方提交一旦入库这条历史就永久记录下来了。一个改动从诞生到进入版本历史需要经历两个阶段先add把它从工作区放入暂存区再commit把它从暂存区写入版本库。很多人只记命令不记概念就会搞混为什么我改了文件但status看不到——那是因为你还没add。3.2 高频命令速查与演示我按使用频率给命令排个序并标注每条命令的常规用途命令用途高频程度git status查看当前工作区和暂存区的状态每天无数次git add file把文件改动放入暂存区每次提交前git add .把所有改动放入暂存区注意别误加看情况git commit -m message把暂存区内容提交成一条历史记录每天多次git log --oneline查看提交历史摘要每天多次git pull拉取远程仓库的最新提交并合并到当前分支每天必做git push把本地提交推送到远程仓库每天多次git diff查看工作区相对暂存区的差异提交前检查git branch查看本地分支列表高频git checkout branch/git switch branch切换分支高频git merge branch把指定分支合并进当前分支常规git stash临时保存工作区改动偶尔有一类我非常想强调的命令是add和commit的组合使用。一个常见场景是你同时改了三个文件但只想提交其中两个有逻辑关系的改动另一个人无关的改动留到下一次提交。正确的做法是精准git add需要的文件而不是图省事直接git add -A。提交粒度是代码审查友好度的重要影响因素一次提交只做一件事这条原则贯穿所有优秀团队。git log --oneline --graph是我看历史最常用的命令。加上--graph后Git会用线条把分支和合并的拓扑关系画出来比单看线性列表清晰很多。3.3 一条完整的上线工作流示例把命令拆开讲太抽象我用一个具体的开发场景串联一遍。假设你新入职一个团队需要开发一个登录功能的优化改动。完整流程是# 1. 把远程仓库代码拿到本地第一次 git clone gitgithub.com:team/project.git cd project # 2. 基于main分支创建一个功能分支 git checkout -b feature/login-optimize # 3. 开发过程中随时查看哪些文件被改动过 git status # 4. 开发完成先看具体改了哪些内容是否有不该改的 git diff # 5. 确认无误把这些改动放入暂存区 git add src/Login.js src/Login.css # 6. 提交一次完整的功能改动 git commit -m 优化登录表单的校验逻辑 # 7. 提交前先把main分支的最新改动拉回来 git fetch origin git merge origin/main # 8. 确保和最新代码一致后推送功能分支到远程 git push origin feature/login-optimize第7步值得多说一句。很多公司要求提交前先同步远端更新。git fetch只把远端的最新提交拉到本地一个远程追踪引用里并不会自动合并到你的工作分支所以需要手动merge。如果你确定当前分支没有未提交的改动也可以直接用git pull——它等价于git fetch加git merge一步到位。我还想提醒一个细节git clone之后在main分支执行git pull其实等价于更新当前分支到远程追踪分支的最新位置。当本地分支和远程分支的追踪关系正确时git pull会自动选择正确的远端。4. 分支管理创建合并到解决冲突的完整链路分支是Git最强大的特性也是很多新人从会提交过渡到会协作的分水岭。4.1 为什么分支是Git的核心优势传统的集中式版本控制系统比如SVN也有分支的概念但创建和切换分支的开销极大而且分支上的操作会影响共享服务器。Git的分支则是一个极其轻量的指针——它指向某个提交对象。创建分支本质上只是写下一个指针所以几乎瞬间完成。这个轻量特性带来了一种完全不同的工作方式你可以随时基于当前状态拉出一个分支做试验成功就合并失败就丢弃成本几乎为零。跨分支协作也变得灵活多个功能可以在不同分支上并行推进互不干扰。日常团队协作的分支规范通常是这样的结构main分支始终处于可发布状态禁止直接在上面提交。develop分支集成各功能的开发分支所有功能完成先合并到这里。feature/xxx分支每个功能一个独立分支从develop拉出完成后合并回去。hotfix/xxx分支线上紧急修复从main拉出修复完成后合并回main和develop。这套结构能应对大多数团队的需求。团队越大分支规范越需要明确。4.2 merge与rebase的选择逻辑合并分支最常碰到的两个命令是git merge和git rebase。很多新人分不清该用哪个其实它们的核心区别只在一句话merge保留分支分叉的历史rebase重写历史使其线性化。具体解释一下。git merge的操作方式是把两个分支的历史接到一起生成一个合并提交merge commit。从git log --graph看起来你会看到明显的分叉和汇聚。它的优点是历史真实反映了开发过程最安全适合在团队协作中作为默认选择。git rebase的操作方式是把当前分支的提交摘下来逐一重新放到目标分支的最新提交之上。结果是历史变成一条直线看起来像这些提交本来就是连续开发的。优点是历史非常清晰整洁适合作为个人功能分支在合并前的整理手段。但rebase有一个重要风险它会重写提交哈希所以永远不要对已经推送到远程且多人共享的分支执行rebase否则会让所有协作者的仓库历史变得混乱。我自己在团队里的默认习惯是功能分支合并到集成分支时用merge但开发者在提交前会先rebase一下集成分支的最新代码保持本地功能分支与最新代码的协同然后再推送合并。这样既保证集成历史是准确的也能尽量避免大型合并冲突。4.3 冲突解决从标记到合并完成冲突解决是很多人的心理障碍实际上它的步骤完全有规律可循。我演示一次最典型的场景。假设你在feature/pay分支上修改了billing.ts同时同事在main分支上也改了这个文件然后你把main合并到你的分支git merge mainGit很快告诉你CONFLICT (content): Merge conflict in billing.ts然后你打开这个文件会看到类似这样的内容function calculateTotal(amount: number) { HEAD return amount * 0.98; return amount * 0.96; main }这三段标记的意思是 HEAD下面到之间是你当前分支的改动。下面到 main之间是你要合并进来的分支的改动。解决办法就是删掉这三行标记保留你真正想要的内容。比如你想保留折扣力度更大的function calculateTotal(amount: number) { return amount * 0.96; }改完后保存然后执行git add billing.ts git commitGit会为你生成一个合并提交冲突解决完成。整个过程里最需要冷静的就是面对那些标记的时刻——它们只是Git把无法自动决定的分歧摆在你面前你删掉标记、写好代码就是完成了一次标准的冲突调解。我提一个提高效率的习惯解决冲突前先用git status看看冲突文件列表然后把每个文件用git diff或IDE的diff工具打开。IDE比如IDEA解决冲突是最舒服的它会把你当前分支、对方分支和合并中的结果三栏并列展示你点选保留哪边即可。当然终端的标记理解能力仍然是必备技能尤其在服务器上操作时没有图形界面。5. 从命令行回到图形界面IDEA里的Git操作全流程做后端开发或者偏工程的朋友大概率在IntelliJ IDEA里写代码。IDEA内置了堪称最强的Git图形插件它不仅仅是一个按钮版git还提供了很多命令行不方便做的可视化管理功能。这一章我讲两个最常见的场景。5.1 在IntelliJ IDEA中导入远程仓库项目入职第一天最常见的就是把公司代码拉下来。操作路径是File - New - Project from Version Control然后在弹出的输入框里粘贴远程仓库的HTTPS或SSH地址选择保存路径点Clone。IDEA会直接完成clone操作并且自动识别远程仓库信息下载依赖后整个项目就能跑起来。这里有几个信息值得新朋友留意在Clone之前确认你已经完成前面章节的SSH配置。如果用SSH地址clone时提示认证失败多半是密钥没配置好或者地址复制错了不要急着跳过。IDEA底部有一个Git工具窗口里面分成了Changes、Log、Console几个标签页。Changes里可以看到当前分支所有未提交的改动Log里可以看到可视化提交历史。如果你在IDEA里做了未提交的改动然后又想放弃某个文件的所有改动可以在Changes面板里选中文件右键选择Revert——它对应的是git checkout -- file这个命令。5.2 创建新项目并推送远程仓库反向操作——你本地新建了一个项目想推送到远程仓库变成一个Git仓库。命令行流程是三步git init git add . git commit -m initial commit git remote add origin gitgithub.com:user/new-project.git git push -u origin master在IDEA里的操作是新建项目后工具栏上的VCS - Enable Version Control Integration选择Git。然后Git - Commit把所有文件提交到本地。再到Git - Push第一次推送时会要求填写远程仓库地址填好后推送即可。注意-u参数的作用是建立本地分支与远程分支的追踪关系IDEA里推送时会自动处理好。两者的步骤一一对应理解命令行再操作IDE就很顺畅。5.3 IDE与命令行协作的注意事项我自己是混合使用的但有几个经验想分享。第一IDEA的Git窗口会跟命令行实时同步。你在终端里执行了commit切回IDEA它的Log窗口会立刻刷新出来。反之亦然。这种同步是没有延迟的因为IDEA是读取.git目录里的真实数据不是独立维护一份假数据。第二用IDEA操作时同样要注意提交粒度。IDEA的Changes面板里有一个非常实用的功能右键文件选择Show Diff或者直接在左侧的变更列表中选择具体文件右侧会展示按行高亮的具体改动内容。你可以只暂存某个文件的部分行选择代码片段后右键Stage Selected Changes相当于命令行的git add -p。这个功能极其好用建议每个人都试一下。第三养成提交前先看Log的习惯。IDEA的Log窗口里可以直观看到提交历史、分支的关系、标签的位置。提交前看一眼当前分支在哪里、目标分支在哪里能够避免很多低级错误。6. 高频报错定位SSH认证失败及其它常见坑即使配置都在碰到报错也再正常不过。这一章把我在实操中最常遇到的几个问题逐一拆开讲清楚排查路径。6.1 SSH认证失败排查步骤ssh: Could not resolve hostname github.com: Name or service not known、Permission denied (publickey)、Host key verification failed这三种报错我都见过处理方式完全不同。先说Permission denied (publickey)。这是最常见也最容易定位的。它的含义是SSH连接建立成功但服务器无法用你的公钥完成验证。逐层排查第一步确认私钥文件是否被配置程序加载ssh -vT gitgithub.com-v参数会打印详细的连接过程重点看有没有出现在加载公钥时的堆栈比如显示Offering public key: /path/to/key RSA SHA256:xxx字样。如果显示没有加载任何密钥文件最常见的两个原因是秘钥文件名不是默认的id_ed25519或id_rsa或者秘钥目录权限不对。非默认文件名需要手动托管ssh-add ~/.ssh/custom_key_name权限问题的话检查~/.ssh目录权限是否为700文件权限是否为600。Windows下用Git Bash运行这些命令也一样。第二步确认公钥是否已经添加到托管平台。对照一下cat ~/.ssh/id_ed25519.pub复制这一段和网页上保存的比对。很多人出错在复制时头尾多了一个空格或者少了一个前缀导致不匹配。第三步确认你连接的地址是不是对。GitHub和GitLab在连接时用的用户名不同gitgithub.com里的git是SSH服务用户名不是你的账号名。不要改它。再说Host key verification failed。这个报错通常是因为你之前从未连接过该主机或者主机密钥在服务端更换了。解决办法是ssh-keygen -R github.com这条命令会移除已知主机列表里的旧记录然后再次用ssh -T gitgithub.com重新信任。6.2 其他常见报错与解决办法除了SSH问题这几个报错的新人出现频率也很高。Failed to connect to github.com port 443: Connection timed out。网络环境导致无法直连目标端口。排查思路是先确认整体网络状况再检查是否有代理设置。Git本身支持代理配置如果你有需要可以这样设置git config --global http.proxy http://127.0.0.1:7890 git config --global https.proxy http://127.0.0.1:7890需要取消代理时git config --global --unset http.proxy git config --global --unset https.proxy我见过很多同事因为曾经设置过代理换了网络环境后导致推送频繁超时最后用git config --global --list检查才发现rob是代理配置残留。所以这个目录最好也留个印象。Updates were rejected because the remote contains work that you do not have locally。本地落后于远程直接push会被拒绝。万能的处理方式确认没有本地新提交或工作区改动后执行git pull --rebase把远程的新提交垫到本地提交下面再尝试push。用rebase而不直接用merge是为了保持当前的提交线路清晰。You have not concluded your merge (MERGE_HEAD exists)。这个情况通常发生在之前一次pull或merge冲突没有解决完就中断了。解决办法很直接要么继续解决冲突完成合并要么确信想取消合并回到之前状态执行git merge --abort。git commit弹出的编辑器很陌生。配置了全局core.editor但依然进入vi多半是core.editor配置没生效或者你用的Git Bash在Windows上找不到路径。VS Code的code命令路径需要提前加入PATH。中文文件名显示为一串带引号的数字。前面提过用git config --global core.quotepath false就能修复。6.3 一个通吃的黄金策略先status再动手不管遇到什么Git异常我的第一步永远是git status它会告诉你当前仓库的真实状态在哪个分支、哪些文件有改动、多少已暂存、多少未追踪、是否处于合并冲突中、是否处于rebase过程中。70%以上的问题看到status输出的那一刻就已经有了方向。如果你是刚接触Git的新手我强烈建议你接受一个小习惯每次执行可能改变仓库状态的命令merge、rebase、reset、checkout之前先花5秒钟看一下status。这比你记住几十条复杂命令都更有效。写到这里我回想自己最初学Git时最受用的一个瞬间是有人跟我说不要怕把仓库弄坏学会看status再学会读log你就有九成的把握把它恢复回来。这句话送给正在读这篇博客的你。你把前面这些概念和命令都过一遍之后再去碰那些更高级的东西——比如git filter-branch、git reflog、submodule、LFS——都会顺利得多。到时候记得回来看看自己是怎么一步步把这些坑踩成经验的。