我最早接触Git是在一次Code Review上有人指着我一行刚推上去的提交记录问“你这个提交怎么不在远程分支上”我当时的表情和你第一次看到fatal: not a git repository (or any of the parent directories): .git时大概是一样的。后来系统地把分布式版本控制工具Git装好、配好、用好才意识到当年的手忙脚乱本质上是脑子里还停留在SVN的集中式模型——以为“提交”就是“上传”以为“分支”就是“目录拷贝”。这篇内容就是把我这些年从安装到日常使用、再到分支合并、历史改写、大文件管理踩过的坑按实际项目里的使用顺序串一遍。无论你是刚装好Git还不知道配置邮箱密钥的新手还是已经提交过几百次但遇到冲突就心慌的老手都可以照着里面的命令和思路直接操作。我不会只给你一份命令清单还会说清楚每个步骤背后的原因——为什么这么配、为什么这个命令要这么写、为什么冲突会出现在这个位置。1. 先想清楚Git和SVN的本质差异再决定怎么学1.1 分布式与集中式的核心区别很多人学Git学得痛苦是因为还在用SVN的思维理解Git。SVN是集中式版本控制所有历史记录都存在服务器上本地只是工作副本你本地改了东西没提交别人永远看不到你提交了但没联网也提交不了。Git是分布式版本控制工具这不是口号是架构。每个开发者clone下来的仓库里都有完整的历史记录、完整的分支、完整的标签。这意味着你本地就是一个完整的版本库不联网也能提交、能看历史、能建分支、能回滚。等你有网络了再push把本地提交推送到远程。我举一个具体的例子假设你在高铁上改代码SVN下你连svn commit都执行不了因为提交必须连接服务器Git下你照样git commit提交记录完整记在本地到站联网后git push就行。这个差异决定了你之后所有操作习惯。1.2 这套模型带来的“反常识”因为本地有完整历史Git里很多操作和你直觉不一样git log查的是本地提交记录不是服务器上的记录。如果没git pull过你看到的“最新”可能有延迟。git commit只是记在你自己的本地仓库别人看不到必须git push之后远程仓库才更新。git checkout切换分支是切换整个工作目录的状态不是SVN那种“复制一份出来”。这些反常识的地方恰恰是新手最容易出错的地方。我见过不少人commit之后以为自己已经“上传”了关电脑回家第二天远程上什么都没有。所以学习Git的第一课不是背命令而是建立“本地仓库-远程仓库-工作区-暂存区”这四者关系的心理模型。用一句话总结SVN是“服务器说了算”Git是“自己先说了算再想办法和别人的本地仓库达成一致”。2. Windows、macOS和Linux下Git安装全程实录2.1 Windows官方安装包和Git Bash的选择Windows安装Git最稳妥的方式是去Git官网下载对应系统版本的安装包。安装过程中有几个选项容易让人犹豫我直接说结论Select Components建议勾选“Git Bash Here”和“Git GUI Here”这两个右键菜单在Windows里非常方便尤其Git Bash给你一个Linux风格的命令行环境。Default editor如果你装了VS Code选择“Use Visual Studio Code as Git’s default editor”没装的话选“Use Nano”也行但每次提交都要写提交信息一个顺手的编辑器很重要。Adjusting your PATH选“Git from the command line and also from 3rd-party software”这个选项会把Git加入系统PATH让你能在PowerShell、CMD和VS Code终端里直接敲git命令。如果你选错了就会遇到VS Code里报“无法将‘git’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”的问题。Line ending conversions默认选项“Checkout Windows-style, commit Unix-style line endings”即可具体原理我后面专门讲。安装完成后在任意目录打开Git Bash输入git --version能输出版本号就说明安装成功。比如输出git version 2.23.0.windows.1这表示你用的是2.23.0版本。2.2 macOS和Linux包管理器最省心macOS上如果装了Homebrew一条命令搞定brew install gitLinux用户更简单Debian/Ubuntu系用sudo apt update sudo apt install gitCentOS/RHEL/Fedora系用sudo yum install gitLinux发行版自带的Git版本往往偏旧如果某些新特性用不了建议通过源码编译或者添加官方维护的软件源安装新版本。但大多数场景下系统自带版本的Git完全够用。2.3 安装完第一件事验证命令、配置换行符不管是哪个系统装完我都建议做一次“体检”git --version git config --global user.name 你的名字 git config --global user.email 你的邮箱--global的意思是全局配置对所有仓库生效。如果你在不同公司用不同邮箱可以去掉--global在具体仓库里单独设置user.name和user.email。这里说一个我早期踩过的坑不配user.name和user.email就去git commitGit会直接报错告诉你“Please tell me who you are”因为它必须在提交记录里写入作者信息。这个信息不同于登录远程仓库的账号它只是提交记录里的署名。3. 那些“默认选项”和“隐藏配置”到底在帮你挡什么麻烦3.1 换行符CRLF和LF的世纪之争Windows里换行是CRLF回车换行Linux和macOS里是LF换行。如果不做转换同一份文件在不同系统间来回修改git diff会显示整个文件都是修改的因为没有内容差异全是换行符号差异。Git给你提供了一组配置项git config --global core.autocrlf true # Windows用户 git config --global core.autocrlf input # macOS/Linux用户core.autocrlf true表示签出代码时把LF转成CRLF提交时把CRLF转成LF。这样你Windows机器上编辑文件是正常的Windows换行但存进仓库的是统一格式。core.autocrlf input表示提交时把CRLF转成LF签出时不转换。这里有个实战建议如果你的团队全是Windows可以全部设置为true如果团队里混用系统更推荐的方案是仓库根目录加一个.gitattributes文件明确指定哪些文件用什么换行符* textauto *.sh text eollf *.bat text eolcrlf这个文件会随仓库走比每个人自己配core.autocrlf靠谱得多能避免团队里“我这边没改啊为什么diff这么多行”的灵异事件。3.2 SSH密钥让远程仓库不再反复要密码配置SSH密钥是让Git不再每次push都输密码的关键。以常见的Gitee和GitHub为例流程都是三件事生成密钥、添加公钥到平台、验证连接。生成密钥ssh-keygen -t rsa -b 4096 -C 你的邮箱一路回车不设置passphrase也可以。生成之后默认保存在~/.ssh/id_rsa.pub和~/.ssh/id_rsa。把公钥内容复制cat ~/.ssh/id_rsa.pub然后登录Gitee或GitHub在“设置 - SSH公钥”里粘贴这段内容。验证是否配置成功ssh -T gitgitee.com ssh -T gitgithub.com如果显示“You’ve successfully authenticated”或者类似的欢迎信息就说明通了。之后clone远程仓库时用SSH地址比如gitgitee.com:xxx/yyy.git就不再需要频繁输入账号密码了。这里有一个细节很多人会忽略ssh-keygen生成的密钥文件默认是id_rsa如果你同一个机器要同时连Gitee和GitHub不建议把两把钥匙都命名为id_rsa。可以用-f选项指定文件名ssh-keygen -t rsa -b 4096 -C 你的邮箱 -f ~/.ssh/id_rsa_gitee ssh-keygen -t ed25519 -C 你的邮箱 -f ~/.ssh/id_rsa_github然后在~/.ssh/config里写清楚每个主机的对应密钥Host gitee.com HostName gitee.com User git IdentityFile ~/.ssh/id_rsa_gitee Host github.com HostName github.com User git IdentityFile ~/.ssh/id_rsa_github这样就不用担心两个平台密钥串用导致认证失败。4. 日常使用频率最高的Git命令按真实场景拆开4.1 从clone到第一次commit的完整链路拿到项目后的标准链路我拆成四步git clone 仓库地址 cd 项目目录 # 创建一个新分支用于开发 git checkout -b feature/my-first-task # 修改文件之后查看状态 git status # 把改动加入暂存区 git add -A # 提交到本地仓库 git commit -m 完成第一个功能开发先说git checkout -b。很多新人直接在主分支master或main上改然后提交、推送这是个人项目问题不大但团队项目里这是一个坏习惯。因为主分支通常是受保护的你没权限直接push到主分支而且直接在主干上开发会让代码评审和回滚变得很麻烦。规范做法是每次需求开一个feature分支开发完合并回去。再说git add -A。-A的意思是包含新增、修改、删除也就是把当前目录所有变化都加入暂存区。如果你只想提交某个文件可以git add src/xxx.java。想看清楚哪些被改了git status会列出暂存区和未暂存的改动。为什么Git要分“工作区-暂存区-本地仓库”三层因为这样你能决定“这个提交包含哪些文件”。比如你同时改了一个bug和一个新功能你可以分两次git add部分文件分别提交保持每条提交记录职责单一。这是SVN没有的能力。4.2 分支操作与合并feature分支合并回主干开发完功能分支要合并回maingit checkout main git pull origin main git merge feature/my-first-task git push origin maingit pull之前一定要做把远程主干的最新变化先拉下来避免你本地主干落后于远程合并的时候产生一堆莫名其妙的问题。如果合并过程顺利Git会自动生成一个merge commit。如果两个分支改了同一个文件的同一个区域就会产生冲突这个下面专门讲。还有一个容易被忽略的命令是删除分支git branch -d feature/my-first-task功能合并完成后删掉本地分支保持仓库整洁。-d只删已合并的分支如果用-D强制删除不管它是否合并过。我的习惯是合并完先确认无误再删分支。4.3 撤销与回滚改了不该改的东西怎么办每个Git用户都会遇到“改错了想撤回”的时候我按改动的阶段区分工作区改乱了、还没addgit checkout -- 文件把工作区文件恢复到上一次提交的状态。但这个命令会丢掉你所有的本地改动且无法恢复用之前要确认。已经add进暂存区、还没commitgit reset HEAD 文件把文件从暂存区移回工作区改动还在只是不暂存了。已经commit了、还没pushgit reset --soft HEAD~1撤销提交但保留改动在暂存区git reset --hard HEAD~1完全丢掉那次提交和改动。这里必须强调--hard的危险性。它会把工作区、暂存区、提交记录全部回退到你指定的那个位置之后修改的内容无法找回。我自己的习惯是能不--hard就不--hard优先用--soft至少改动内容还在。5. 提交历史修整commit --amend和rebase -i的正确打开方式5.1 commit --amend改上一次提交git commit --amend这个命令的作用本质上是把当前暂存区的内容如果有的话合并到上一次提交里生成一个全新的提交替换原来的提交记录。最常见的用法是“上次提交信息写错了想改信息”git commit --amend -m 修正后的提交信息然后是“上次提交漏了一个文件”git add 漏掉的文件 git commit --amend --no-edit--no-edit表示沿用原来的提交信息不打开编辑器。这里有一个必须记住的红线--amend会改写提交历史所以只能用在“还没push到远程”的提交上。如果你已经git push了再git commit --amend本地和远程的记录就对不上了下一次push会被拒绝需要git push --force强制推送。强制推送会覆盖远程历史如果是团队共享分支别人那边会乱套千万不要对多人使用的远程分支做强制推送。5.2 rebase -i整理多个提交当你提交了5次想让它们合并成一个清晰的提交用git rebase -i HEAD~5Git会打开一个交互界面里面列出最近5次提交pick 1a2b3c4 第一次修改 pick 5d6e7f8 修复空格 pick 9g0h1i2 第三次调整把后面几个的pick改成squash表示把这些提交合并到前一个里squash 5d6e7f8 修复空格 squash 9g0h1i2 第三次调整保存退出后Git会让你重新编辑合并后的提交信息。这样原本5条“开发过程中不断修改”的记录就变成了一条干净的提交。这个操作在代码评审时非常有用评审人只需要看一个最终的完整改动不需要看到你中间试了两次的中间过程。同样rebase -i也是改写历史规则和--amend一样别动已经推送的分支。6. 分支合并冲突实战从恐慌到从容6.1 冲突的本质其实很简单冲突不是Git出错了而是Git在说“我不知道你们两个人谁是对的需要你来决定。”它源于两个分支修改了同一文件的同一段内容Git不敢擅自覆盖于是把决定权交给人类。我举个具体例子你和同事同时改了config.js里的apiUrl这一行他改成http://old-server你改成http://new-server。当你们的分支要合并时Git发现同一行有两个不同版本它无法选择于是产生冲突。冲突产生后相关文件会变成这样的标记 HEAD http://old-server http://new-server feature/new-configHEAD表示当前分支的版本下面是你要合并进来的分支的版本。6.2 解决冲突的标准流程第一步打开冲突文件看到上面那种标记后手动选择保留哪部分或者手动改成一个新的混合版本然后删除标记符号。第二步改完所有有冲突的文件后执行git add . git commit注意合并冲突解决后的提交信息是Git自动准备好的内容是类似“Merge branch xxx into xxx”的标准信息直接保存退出即可。第三步验证。在合并完成后编译、跑测试确认没有因为合并引入新的问题。我的实操习惯解决冲突时不要只在编辑器里看那几个标记而是用git diff把两个版本的前后文都看一遍因为有些冲突不是同一行而是两个分支在一段区域内分别改了不同行合并后的逻辑可能完全不成立编译器不一定能发现这种问题。6.3 merge和rebase的冲突解决差异同样合并一个功能分支git merge feature/xx是把feature的改动并到当前分支生成一个merge commitgit rebase feature/xx是把当前分支的提交“搬”到feature分支的顶端重放一遍形成一个线性的历史。rebase的优点是无脑好看提交记录是直线没有分叉。缺点是它也在改写历史会让远程分支和你本地的提交关系变得复杂。如果分支已经推送过绝对不要rebase。在冲突上merge只需要解决一次冲突rebase如果有3条提交可能要解决3次冲突因为每次重放都可能撞车。所以新人建议先用merge把主线逻辑跑通再去学rebase。7. 解决大文件和多任务场景的两个进阶武器7.1 Git LFS把大文件从仓库历史里摘出去Git本身对二进制仓库不友好。一个几百MB的设计稿、模型文件、数据集提交进去仓库体积立刻膨胀clone速度变慢而且这些历史永远存在。Git LFSLarge File Storage就是干这个的它把大文件内容存到单独的LFS存储里Git仓库里只保存一个轻量的指针文件。安装LFSgit lfs install在仓库里指定哪些文件走LFSgit lfs track *.psd git lfs track *.zip git add .gitattributes git commit -m 配置LFS规则之后新增的.psd和.zip文件会自动按LFS方式管理。使用LFS常见的坑是“clone卡住”。git lfs clone在拉取大文件时如果网络不畅确实会卡住。多数情况下是因为LFS文件较大而网络带宽有限不是命令执行错误。可以尝试在clone时跳过LFS文件只clone Git历史之后按需拉取git clone --no-checkout 仓库地址 cd 仓库目录 git lfs pull如果git lfs pull也卡住先检查.git/lfs/objects里是否有已下载的部分文件再确认LFS服务端地址配置是否正常。另外一个高频问题有人把大文件在没配置LFS时就提交进了仓库后来再配置LFS已经来不及了因为老的大文件还留在历史里。这种要用git filter-repo这类工具重写历史相当麻烦。所以LFS一定要在项目早期就配置。7.2 Git worktree一个仓库同时开多个分支日常开发里你会遇到一个尴尬当前分支改了一半突然有个线上bug要紧急修复但又不想提交现在改到一半的代码。git worktree就是一种优雅解法。git worktree允许你在同一台机器上同时签出多个分支放在不同目录里互不干扰。命令是git worktree add ../hotfix-folder hotfix/bug-001这会在hotfix-folder目录里创建一个新的工作区关联到hotfix/bug-001分支。你可以在这个新目录里修改、提交、推送完全不影响原来那个还没改完的分支。用完删除git worktree remove ../hotfix-folder这个工具非常适合“开多个分支并行”的场景也比来回git stash再恢复安全得多——stash如果处理不当很容易丢改动。8. 疑难杂症排查清单从命令找不到到SSH认证失败8.1 SSH认证失败一步步找出问题所在git push时报SSH认证失败错误信息里常常带着Permission denied (publickey)或Could not read from remote repository。我的排查顺序是固定的第一步确认你用的是SSH地址还是HTTPS地址git remote -v如果是https://开头的地址但你的SSH密钥没配到平台就会认证失败。可以直接改用SSH地址git remote set-url origin gitgitee.com:用户名/仓库名.git第二步确认SSH密钥是否加载到了当前会话ssh-add -l如果显示“The agent has no identities”说明私钥没加到ssh-agent里eval $(ssh-agent -s) ssh-add ~/.ssh/id_rsa第三步确认公钥确实配到了远程平台。用cat ~/.ssh/id_rsa.pub查看公钥内容再去平台后台比对。我最常遇到的情况其实是第一台机器改了用户名之后~/.ssh/config里还残留着旧的IdentityFile配置导致SSH一直在用错误的密钥。删掉或改对之后立刻恢复。8.2 “git不是内部或外部命令”Windows环境下的排查法Windows下运行git命令提示无法识别无非三种情况Git没装成功重新下载安装包静默安装一次。Git装了但没加入PATH安装过程中没勾选“Add to PATH”或者勾选错了。处理方式是找到Git安装目录下的cmd子目录把完整路径加到系统环境变量PATH里。刚装完没重启终端PowerShell或CMD的环境变量没刷新重新打开终端窗口即可。VS Code里报同样错误时除了系统PATH还要检查VS Code的git.path设置。有时候VS Code在第一次启动时记忆了错误的Git路径需要打开设置搜索git.path改成Git的实际安装路径比如C:\Program Files\Git\cmd\git.exe。8.3 .git目录泄露一个不该被忽略的安全底线git目录泄露这个话题在安全圈里是个经典问题。简单来说如果网站部署时把.git目录也传到了web服务器上访问者就能通过浏览器直接访问.git/config文件从而下载整个仓库历史源码全部暴露。这种情况如果发生在你自己部署的站点上解决方法是确认服务器上没有暴露.git目录配置web服务器规则禁止访问以.git开头的路径同时确保CI构建产物里不包含这个目录。8.4 补一个IDE场景IntelliJ IDEA里拉取项目标题热词里有“diea创建新项目拉取git”应该指的是IntelliJ IDEA。IDEA直接支持从Git仓库拉取项目选择“Get from VCS”填入Git仓库URL选择存放目录点Clone即可。如果IDEA提示找不到Git设置里面搜索Git指定Git安装路径。IDEA内置的Git图形界面能完成add、commit、push、pull、merge这些基础操作但别完全依赖图形界面因为遇到冲突、复杂rebase、LFS问题时命令行永远是更可靠的退路。9. 日常操作中的几个“别犯法”提醒Git有一个“改了推送历史就相当于对团队动手术”的特性我用一个红线总结别对已经推送的共享分支执行git commit --amend、git rebase -i等改写历史的命令。别在main分支上直接开发哪怕是一个人的项目。别把密码、Secret、Access Key提交进仓库因为Git历史会永久记录它们即使你后面删掉文件历史里依然能翻出来。别在合并冲突没解决完时git pushGit会拒绝但你要分清是“拒绝”还是“冲突没解决完”。别为了省事把.git目录塞进部署包。我自己曾经在线上环境的一个配置文件里顺手写了一个数据库密码然后提交、推送直到两天后安全扫描才发现。当时解决的办法是用git filter-repo重写所有历史然后强制推送并且要求所有同事同步仓库。整个过程痛苦不堪。从那以后我的原则是凡是涉及密码、密钥的文件一律加入.gitignore永远不提交。拦截条目的配置也就是把需要排除的文件写入.gitignore是整个Git使用里被提到最多、却最容易被忽略的环节。常见的排除项包括IDE配置目录.idea/、.vscode/、构建产物target/、dist/、build/、环境配置文件.env、config/local.js。.gitignore不是可有可无的它是保护你隐私和仓库卫生的第一道防线。再分享一个小技巧如果你已经把本应忽略的文件提交了可以先把它加入.gitignore然后用git rm --cached 文件把它从Git索引里移除提交后文件会保留在本地的磁盘上但不再被Git追踪。这样可以不动代码内容只把“已经不该被追踪”的这个事实修好。回到最开始的话题。Git这个分布式版本控制工具和SVN那个时代最大的不同是它把“协作”这件事的权力和责任都下放到了每个开发者手里。你提交的很可能是错的内容合并的很可能是冲突改写历史的命令很可能让你后悔。但这些都不是Git吓唬你的理由恰恰是它给了你足够的工具去修复问题——只要你还记得备份好重要的分支记得推送之前多看一眼git status和git log。把基础命令练熟把红线记牢把那几个进阶工具在真正需要它们的时候拿出来你就能从“会敲命令”变成“用Git解决实际问题的人”。Git还有很多值得挖掘的玩法比如hooks钩子、bisect二分查找、filter-repo历史清洗但我的建议是先把这篇文章里的每一步在自己的操作环境里跑一遍尤其是用一个临时仓库故意制造一次合并冲突亲手解决一次再面对真实场景时你就不会慌了。