Git指令实战:从配置、分支合并到撤销回滚的完整指南
很多人对Git敬而远之是因为感觉它指令太多、太抽象。我当年学Git也是靠死记硬背背一个用一个是常态直到有一次在分支合并时把代码搞得一团糟push又被远端拒绝大半夜对着终端发呆才真正想明白Git的核心不是那些命令长什么样而是提交历史如何被组织、如何被回退、如何被多人协作。这篇文章我就把自己这几年高频使用的Git指令完整梳理一遍覆盖从下载安装、环境配置、日常提交、分支合并到远程仓库对接、IDEA拉取项目、撤销回滚这些真实场景把每一个指令背后的逻辑也一并讲清楚。不管你是刚装好Git还没配置环境的新手还是已经写了一阵子代码但对merge和rebase始终分不清的老熟人这篇应该都能让你重新理解一遍Git。1. 装Git只是开始安装与环境配置里的那些容易忽略的细节1.1 下载安装版本选择和一路Next背后到底发生了什么Git的下载安装本身不算复杂但很多人装完就以为完事了后面频繁踩坑。先说下载官方站点提供Windows、macOS、Linux各平台的安装包Windows下一般下载exe安装包即可。安装过程中有几个选项值得多说一句因为默认选项未必适合所有人。比较关键的是PATH环境变量那一项默认推荐的是Git from the command line and also from 3rd-party software意思是在CMD和PowerShell里也能直接敲git命令。如果你平时用Windows自带的终端比较多就保持这个选项如果主要用IDE内置终端其实怎么选影响不大但强烈建议别选only from Git Bash否则后续在别的终端里调用git会提示命令不存在。另一个值得留意的是换行符转换选项也就是line ending。安装向导里有三个选择默认是Checkout Windows-style, commit Unix-style line endings自动把CRLF转成LF再提交。这个默认行为在绝大多数团队项目里是合理的但如果你的项目比较特殊或者团队成员跨平台混用建议在仓库根目录放一个.gitattributes文件来统一规则而不是完全依赖安装时的全局配置。安装完成后可以在终端里输入git --version验证一下能输出版本号就说明装好了。这一步看起来简单却是我见过最多装完却用不了的翻车现场十有八九是PATH选项没选对。1.2 用户信息与换行符不配置好后面全是莫名其妙的报错装好Git之后第一件事不是急着建仓库而是配置身份信息。很多人直接跳过这一步结果commit的时候看到Please tell me who you are的报错或者提交记录里出现一串奇怪的角色名都很正常。git config --global user.name yourname git config --global user.email youremailexample.com这两个配置的作用是给每一次提交盖上签名提交历史里会显示是谁在什么时间改了什么。--global表示对当前用户所有仓库生效如果你在某个特定仓库里想用不同身份可以在仓库目录下不加--global单独配置。我个人建议邮箱一定要填真实有效的因为很多代码托管平台会用它关联你的账号头像和提交贡献图如果随便填一个你的提交就算push上去也不会被算到你的名下。还有一个容易被忽视的配置项是默认分支名。较新版本的Git安装包在init时会提示默认分支用master还是main公司项目通常有统一规范。如果没有特殊要求我建议用main或者直接执行git config --global init.defaultBranch main这样以后所有新建仓库的默认分支都是main省得每次手动改。另外关掉git config --global --list可以查看当前所有全局配置排查问题时很好用。1.3 SSH密钥让每次push不再输密码的关键一步用户信息配好之后接下来值得做的就是把SSH密钥配好。这一步很多人嫌麻烦直接选择每次push时输账号密码HTTPS方式短期看确实省事但次数多了就会发现很难受而且在某些平台HTTPS的密码认证已经被逐步淘汰。生成SSH密钥只需要一条命令ssh-keygen -t ed25519 -C youremailexample.com一路回车会在~/.ssh/下生成一对公钥和私钥id_ed25519.pub是公钥可以随便给别人看id_ed25519是私钥绝不能泄露。然后把公钥内容添加到代码托管平台的SSH Keys设置里再执行ssh -T gitgithub.com不同平台地址不同测试连通性。这一步成功后以后clone和push都不用再输密码了。这里有个经验之谈很多人觉得SSH配置麻烦是因为没理解公钥和私钥的关系。你可以把私钥想象成一把钥匙公钥想象成锁芯你把锁芯交给服务器每次连接时用钥匙去对锁服务器验证通过就放行。理解了这一点配置过程就不会觉得是在瞎操作了。2. 本地仓库的日常循环add、commit、status、log的正确打开方式2.1 第一次提交init、add、commit的组合拳进入一个新项目目录第一件事是git init它的作用是把这个目录变成Git能管理的仓库生成一个隐藏的.git目录你的所有版本历史都在里面。很多初学者会疑惑.git目录越来越大怎么办其实这是正常现象它是你提交历史的物理存储所以有些人会把.git目录理解成时光机的黑匣子一点也不夸张。初始化之后写几个代码文件然后执行git add . git commit -m init projectgit add .把所有修改过的新文件加入暂存区git commit把暂存区的内容固化成一次提交。这里有必要把这三个区域讲透工作区是你实际编辑文件的目录暂存区是你声明这次提交要包含哪些改动的中间地带版本库则是已提交历史的仓库。你可以用这个类比来理解——工作区是厨房暂存区是托盘提交是把托盘端到餐桌上的动作。只有放在托盘上的菜add过的文件才会被端上桌commit。有个高频困惑是git add .和git add -A的区别。简单说git add .会把当前目录及子目录下的新增和修改加到暂存区但在某些Git版本里不会处理删除操作git add -A则会把所有变更包括删除都记录进去。我自己习惯用git add -A尤其是在代码重构、删除文件比较多的时候能避免删掉的文件还留在版本控制里的坑。2.2 status和log读懂Git在跟你说什么git status可能是你之后用得最频繁的指令它告诉你当前仓库处于什么状态哪些文件改了还没add哪些add了还没commit哪些是未跟踪的新文件。输出里通常会有一段英文提示告诉你下一步可以执行什么命令很多人不看提示遇到问题就慌其实Git已经把答案写在脸上了。git log则用来查看提交历史默认输出简洁版commit哈希值、作者、日期、提交说明。配合几个参数会更好用git log --oneline --graph --all --decorate--oneline让每条提交只占一行--graph用字符画出提交历史的分支走向--all显示所有分支的历史--decorate标出分支和标签指向的位置。这串参数组合起来基本就是终端版的提交历史全景图尤其是分支多的时候用图形式输出比纯列表直观得多。我见过不少同事只靠IDE的图形界面看历史命令行日志完全看不懂。我的态度是IDE要看命令行的log也要会看因为服务器排障、脚本巡检这些场景没有图形界面你能依赖的还是这几个基础指令。多练习几次就会习惯从哈希值、HEAD指针、分支关系这些角度去理解仓库状态了。2.3 .gitignore与stash两个容易被忽视的实用工具日常开发中有两类文件不该进版本库一类是构建产物比如node_modules、target目录另一类是本地配置比如IDE的.idea文件夹、.env环境变量文件。解决办法是在仓库根目录放一个.gitignore文件里面写明要忽略的路径或通配规则。node_modules/ target/ *.log .env .idea/写好之后这些文件就不会出现在git status里了也不会被误add进去。我踩过的一个典型坑是没有先写.gitignore就把node_modules加进去了导致提交体积巨大后续clone慢到怀疑人生。如果已经发生了可以用git rm -r --cached node_modules把它从版本控制里移除但保留在本地磁盘上然后再补上忽略规则。另一个实用指令是git stash它的作用是把你当前工作区的改动暂时收起来让你能切到别的分支处理急事之后再恢复现场。比如正在A分支写需求领导突然让切到B分支修一个线上bug你又不想把这堆没写完的代码commit上去就可以git stash push -m feature xxx in progress git stash list git stash poppop会把最近一次stash的改动恢复到工作区同时把stash记录删除。如果恢复时发生冲突也没关系解决方式和merge冲突一样。这里提醒一句stash也是可以存多份的记得用git stash list查看用git stash apply而不是pop可以保留stash记录适合在多个分支间反复借用同一份改动时用。3. 分支合并最全实战merge、rebase到底怎么选冲突又如何处理3.1 分支的创建与切换弄清HEAD和工作区的关系分支是Git最核心的设计也是很多初学者绕不明白的地方。我先给个直觉理解分支其实只是一个指向某次提交的可移动指针HEAD则是一个指向当前所在分支的特殊指针。你执行git branch feature本质上是创建了一个名为feature的指针它指向当前HEAD所在的提交然后git checkout feature或git switch feature把HEAD切换到feature指针上。git branch feature git switch feature我见过一个经典误区以为创建分支会把代码复制一份其实不是。所有分支共享同一个对象库分支只是历史的不同时间线所以切换分支非常快因为只是动一个指针而已。但这里有个容易被坑的点切换分支时如果工作区和暂存区有未提交的改动而这些改动和目标分支有冲突Git会拒绝切换。所以切分支前最好养成先git status看一眼的习惯有改动就先commit或stash。日常用的时候我强烈建议用git switch而不是git checkout来做分支切换。checkout的职责太杂既能切分支又能恢复文件新手容易混switch是Git 2.23之后专门为分支切换设计的命令语义清晰报错也友好。这不是强迫症而是减少认知负担。3.2 merge和rebase的本质区别从提交历史的角度理解合并分支的时候git merge feature和git rebase master是两种路线很多人纠结选哪个。先说merge它会把两条分支的历史合并成一个新的合并提交历史是分叉又汇合的形状保留了完整的协作痕迹。rebase则不同它把当前分支上的提交一个个摘下来重新接到目标分支的最新提交后面历史变成一条直线。用提交历史来比喻merge像是在两条路上架一座桥桥本身成了一次提交rebase像是把其中一条路的里程碑全部搬到另一条路上重新立一遍历史干净但原时间点消失了。这个区别直接决定了适用场景如果你希望保留真实协作过程比如功能分支开发周期长、参与人多merge更合适因为回溯的时候能看清这东西是哪条线并进来的如果你希望历史简洁线性比如提交到主干前清理提交历史rebase更合适。我自己的原则是功能分支和主干合并用merge主干上的个人分支同步最新代码用rebase这样主干历史干净功能合入痕迹完整。3.3 一次冲突解决的全过程从冲突标记到最终合并冲突是合并绕不开的话题也是很多人最怕的环节。实际上冲突不可怕可怕的是不知道冲突长什么样、不知道怎么处理。下面用一个最典型的场景走一遍完整链路。假设我在master分支修改了README.md的第一行并提交同事在feature分支也修改了同一行并提交现在我在master分支执行git merge featureGit会告诉你存在冲突README.md变成特殊状态。打开这个文件你会看到 HEAD master分支的修改内容 feature分支的修改内容 feature HEAD到之间是当前分支的内容到 feature之间是待合并分支的内容。你需要做的不是全部保留或全部删掉而是结合需求决定保留哪个、删掉哪个、还是手动改成新内容然后把这三行冲突标记全部删除。保存文件后执行git add README.md git commit -m merge feature and resolve conflict冲突就解决完了。这里有一个实战建议解决冲突时不要只盯着冲突标记里面的几行看最好把整个文件读一遍。因为有时候两个分支虽然冲突标记只标了其中几行但逻辑上彼此关联只修标记内的内容可能漏掉真正的合并问题。我自己就吃过这个亏冲突标记清干净了代码却跑不起来白白debug了半天。3.4 rebase的注意事项为什么说改了公共历史就别乱动rebase虽然好用但有一条红线必须讲清楚不要对已经推送过、且别人也在用的公共分支执行rebase。原因很简单rebase会改写提交的哈希值原本你push到远程的提交和本地重放出来的提交已经不是同一批对象了别人如果基于旧历史继续开发你再push就会被拒绝或者导致历史出现混乱的双胞胎提交。举一个具体场景你在主分支上rebase了master然后强行推送到远程你的同事本地还留着旧的master历史他下次pull会莫名其妙出现大量冲突或重复提交。这种失控非常难恢复比merge冲突麻烦一个数量级。如果确实需要整理自己的功能分支历史请遵守这条流程在功能分支上执行git rebase master然后git push --force-with-lease推送。注意我写的是--force-with-lease不是--force两者的区别是前者会在推送前检查远端是否变成了别人动过的状态多了一层保护。这也是Git社区推荐的做法能最大程度避免误伤队友。4. 远程仓库对接与IDEA拉取从clone到日常push的完整链路4.1 remote的添加与管理多个远程仓库并存的经验本地仓库和远程仓库的关系是通过remote管理的。用git remote add origin 仓库地址把本地仓库和一个远程地址绑定origin是这个远程仓库的别名可以随意命名但origin是约定俗成的默认名。git remote add origin gitgithub.com:user/repo.git git remote -vgit remote -v列出所有远程地址查看时经常有人发现同一个origin下有fetch和push两个地址。这是因为某些平台的代码托管支持拉取走只读的https、推送走ssh之类的策略不是异常不用管。了解这点就够了不用纠结。我实际开发中还有一个经验一个本地仓库可以挂多个remote比如一份代码同时提交到公司内网仓库和开源镜像仓库。做法很简单就是加两个不同名字的remotepush的时候指定名字即可git remote add internal gitinternal:/repo.git git push internal master这样做的好处是避免同一份代码在两地维护时靠拷贝文件这种原始方式同步坏处是要记住push到哪个remote操作时要格外谨慎。4.2 clone、fetch、pull、push四个指令的分工与配合git clone是把远程仓库完整复制到本地包括所有分支、标签和提交历史等于一次性复制了别人项目的全部状态。它内部做了一件容易被忽略的事自动把远程地址设置为origin并创建本地master/main分支跟踪远程同名分支。日常协作中git fetch、git pull、git push三者的关系需要掰开看。fetch只做一件事把远程的最新提交下载到本地的远程跟踪分支比如origin/master但不动你的工作区文件也不会自动合并。pull则是fetch加merge的快捷组合把远程更新拉下来并合并到当前分支。push则是把本地分支的新提交推送到远程。git fetch origin git pull origin master git push origin feature我对新手有个建议刚开始不熟悉的时候多用fetch再看状态少用pull。因为pull一旦合并出冲突你就同时面临不熟悉合并和不熟悉远程协作两个难题容易手忙脚乱。先用fetch把远程变化拉到本地用git log看清楚差异再决定什么时候merge或者直接pull会让你对仓库状态始终有掌控感。等熟练之后日常场景直接pull完全没问题。4.3 IDEA中创建新项目并拉取Git图形化操作背后的指令逻辑IDEA里和Git相关的操作本质上都是把上面这些指令包装成了按钮但理解指令逻辑会让图标操作更不容易出错。常见的场景是团队已经在远程仓库有了项目你要在本地IDEA里拉取并开始开发。操作路径是IDEA的欢迎页选择Get from VCS或者在菜单栏File New Project from Version Control粘贴远程仓库地址点击Clone。这个动作对应终端里的git clone。Clone完成后IDEA会自动识别项目并导入依赖右下角可以看到当前分支信息。拉下来的项目在IDEA里首次打开时经常需要设置一下项目的JDK和SDK这跟Git无关但很多新手会误以为是clone出了问题这里提一句免得排查错方向。clone成功的标志是右下角分支名正确、项目文件都能正常显示。之后的日常操作在IDEA里对应关系是这样的快捷键CtrlK对应commitCtrlShiftK对应push更新项目对应pull右键文件可以打开Git菜单执行add、stash、rollback等操作。我建议新手前期用IDEA的图形界面观察Git状态命令行作为辅助理解等弄明白每一步背后是哪个指令之后再逐步转向终端操作也不迟。4.4 push被拒的常见原因与解决办法push被拒是远程协作中最常见的报错典型提示是! [rejected] master - master (fetch first)或者failed to push some refs to。原因几乎只有一个远程分支上有你本地没有的提交Git拒绝让你的提交覆盖远程历史。正确的解决思路不是硬推而是先把远程变化拉下来合并。基础操作是git pull origin master这里的pull会先fetch再merge合并完本地重启跑测试确认没问题再git push origin master。如果远程和本地改动涉及相同文件合并时会产生冲突按第3章讲的方法处理即可。还有一种情况是远程分支上有历史提交被改写过比如同事rebase了公共分支这时候普通pull也可能拉不下来报refusing to merge unrelated histories之类的提示。遇到这种要先和团队沟通清楚确认历史确实被有意改写了再决定是否用--allow-unrelated-histories强制合并或者走--force-with-lease重新推送。不要不问缘由就加--force硬推那是给整个团队埋雷。5. 撤销与回滚的指令矩阵checkout、reset、revert、clean该选谁5.1 checkout文件级别和工作区级别的撤回撤销操作是Git里最容易让人混淆的区域因为指令很多各自动的层级还不一样。先说git checkout它有两大用途一是切换分支二是丢弃文件改动。很多人混用导致误操作所以Git 2.23之后官方拆出了git switch管分支、git restore管文件恢复。git restore README.md这条命令会把工作区里还没提交的修改全部丢弃恢复到上一次提交的状态。注意这个操作不可逆本地没备份的改动会直接消失。所以执行前一定要确认可以用git status和git diff先看清楚自己到底改了什么。如果只是改着玩不打算保留这个指令很爽但如果是改了一下午的代码千万别用。5.2 reset的三种模式soft、mixed、hard到底动了什么git reset是用来撤销提交的指令它有三种模式区别在于移动HEAD指针的同时是否动暂存区和工作区。这是理解reset的关键。git reset --soft HEAD~1 git reset --mixed HEAD~1 git reset --hard HEAD~1--soft只移动HEAD指针也就是撤销一次commit但保留所有改动在暂存区适合你刚commit完发现漏了文件想重新commit的场景。--mixed是默认模式撤销commit并把改动退回工作区适合要重做提交、但想重新组织文件的情况。--hard最危险直接丢弃提交记录和所有改动适合彻底放弃某些错误修改的提交。用生活场景类比soft相当于后悔药药吃了但菜还摆在桌上mixed相当于连桌布都撤了菜退回厨房hard相当于把厨房菜也倒了彻底没有回旋余地。所以reset --hard我用得极少只在确定这些提交完全无用的时候才碰。5.3 revert与reset的选择已经推送的提交如何安全撤销如果提交已经push到远程直接reset并强推是很危险的操作前面讲过原因。这个场景应该用git revertgit revert commit-idrevert不是删除历史而是生成一个新的提交这个提交把某个旧提交的改动反向应用一遍相当于用新提交来抵消旧提交。历史完整没有改写任何已发布的内容队友pull下来只会看到一次新的提交不会产生冲突和混乱。使用revert时注意一点git revert接收的是某次提交的哈希值你需要先用git log找到那个提交。如果只想撤销最近一次提交可以写git revert HEAD。处理到一半想退出git revert --abort随时可以把这次反向合并操作取消回到操作前状态。5.4 撤销场景速查表不同撤销场景对应的指令我整理了一张表这也是我给自己团队做培训时用到的版本场景推荐指令说明文件改了还没add想丢弃改动git restore 文件名不可恢复先看diff文件已add想从暂存区退回git restore --staged 文件名保留工作区改动想重新提交上一次commitgit reset --soft HEAD~1改动还在暂存区想撤销commit并重做git reset --mixed HEAD~1改动退回工作区想彻底丢弃最近提交git reset --hard 目标commit慎用不可恢复已推送的提交需要撤销git revert 提交id生成反向提交安全删除未跟踪的新文件git clean -fd检查清楚再执行git clean是这组指令里存在感较低但很实用的一条-f是force强制删除-d是连目录一起删。我建议先执行git clean -n预览它会删哪些文件确认无误后再真正执行否则手一抖删除关键文件就追悔莫及了。说到最后我最想分享的一条经验是Git没有那么多魔法指令常用的核心指令翻来覆去也就二三十个。真正决定你用得顺不顺手的是对提交、分支、远程这三个层次的心智模型是否清晰。遇到问题别急着搜索一条命令解决先停下来想清楚问题出在哪个层级、你想让仓库恢复到哪个状态再去选指令会靠谱得多。自己实际开发中我也曾因为懒得看git status直接reset结果把别人的提交搞丢了后来养成了在任何破坏性操作前都先git status、git log看一下的习惯。这个习惯说不上高级但确实帮我躲过了不少次灾难。如果这篇文章只留一句话给你那就是破坏操作之前先看清状态再看清历史。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →