Git这个工具只要是写代码的基本都绕不开。但说实话很多人对Git的认知停留在git add .、git commit -m update、git push这三板斧上遇到稍微复杂一点的场景——比如改错了想撤销、提交信息写错了、分支合并不顺——就开始慌了甚至有人直接删掉整个目录重新clone。这篇内容我打算从安装配置讲到日常高频命令再讲到撤销、分支、合并这些进阶操作最后把我在实际项目里踩过的坑和排查思路整理成一份速查清单。无论你是刚入门的新手还是用了一年半载但一直是“复制粘贴流”的开发者这篇内容应该都能让你对Git有一个更系统、更踏实的认识。1. 内容整体设计与思路拆解1.1 为什么几乎每个项目都离不开版本管理先说点务虚的。很多初学者觉得版本管理就是把代码存个档其实远不止这么简单。我见过不少小团队早期就两三个人写代码用微信或者U盘互传文件改个bug都要语音通话确认“你改了哪个文件”。等到项目稍微跑起来代码量上千个文件之后这种模式必然崩盘——你根本不知道当前代码处于什么状态也不知道哪次改动破坏了什么。版本管理工具解决的正是这三个核心问题历史追溯、多人协作、分支隔离。历史追溯让你可以回到任意一个时间点的代码状态多人协作让你和别人改同一份代码不至于互相覆盖分支隔离让你可以放心地尝试新功能坏了也不影响主线。Git是目前这个领域事实上的标准。它和SVN这类集中式版本管理最大的区别在于分布式的设计每个人的本地都是一个完整的仓库不是只有一份代码副本。这意味着你可以在没有网络的情况下完整地看历史、建分支、做提交等有网络了再同步到远程。这个设计带来的额外好处是容灾性强——任何一个开发者的本地仓库都足以恢复整个项目。1.2 这篇教程要帮你解决什么问题我写这篇内容的目标很明确让一个对Git完全陌生的人看完之后能独立完成从安装到日常开发的全部流程同时让一个已经会基础操作的人补上撤销、回退、分支合并、冲突解决这些容易踩坑的环节。全文的讲述逻辑按照实际开发中的使用频率来安排。先讲安装和初始化配置因为这是所有操作的前提再讲日常最常用的提交、推送、拉取然后深入到撤销与回退、分支与合并、远程协作最后整理一份常见问题速查表。每一块我都会把命令背后的原理讲清楚而不是只告诉你“敲这个就对了”。比如git checkout和git reset的区别、git merge和git rebase的取舍搞懂这些你在遇到问题的时候才能自己判断用哪个命令而不是碰运气。2. 安装与初始配置2.1 三个平台下的Git安装方式安装这一步看似简单但不同平台确实有需要注意的细节。Windows平台最直接的方式是去Git官网下载安装包一路Next装完。需要注意的选项有两个。一个是安装路径建议不要装在带空格的目录下有些老的构建工具对空格路径支持不好。另一个是行尾结束符的配置安装过程中会问Checkout Windows-style, commit Unix-style line endings还是Checkout as-is, commit as-is建议选第一项。Windows下如果不做转换你在本地编辑文件时是CRLF提交到仓库后别人在Linux上拉下来就会看到一堆莫名其妙的改动。Git帮你自动转换可以避免这种情况。macOS平台系统自带的Git版本通常比较老建议使用Homebrew安装最新版。命令是brew install git。装完之后运行git --version验证一下。Linux平台Debian系用sudo apt install gitRed Hat系用sudo yum install git。Linux下的安装基本不会出问题注意一下发行版软件源里Git版本的时效性如果太老可以添加官方源或者编译安装。这里额外说一点Windows用户装完Git之后我建议你顺手把Git Bash的终端字体调大一点默认的Consolas字体在1080p下看长了眼睛累。这是个人体验问题但会切实影响你敲命令行的心情。2.2 安装后的基础配置user.name与user.email装完Git之后第一步配置就是设置你的用户名和邮箱。这个配置不是随便填着玩的因为它会写进每一次的提交记录里是追溯代码作者身份的依据。git config --global user.name your name git config --global user.email your.emailexample.com--global参数表示全局生效对当前用户的所有仓库有效。如果你在某个特定项目里想用不同的身份可以在项目目录下不加--global重新配置一次项目内的配置会覆盖全局配置。实际工作中这种情况不少见比如个人电脑上同时维护公司项目和个人开源项目用的就是两套身份。验证配置是否成功运行git config --list会输出当前的完整配置列表。如果你看到user.name和user.email的值都正确说明配置好了。我还见到有同学在设置user.name时用了中文名提交记录里确实能显示中文但某些第三方工具比如一些老旧的CI系统在解析中文作者名时会出现编码问题。建议优先使用英文字符同时这个可以和你Gitee、GitHub账号的昵称保持一致方便别人认出你。2.3 SSH密钥配置免密推送的关键一步使用HTTPS方式克隆和推送代码时每次都要输入账号密码虽然可以借助系统的凭据管理器记住密码但体验依然不如SSH方式顺畅而且HTTPS方式在某些网络环境下还会受代理影响。配置一次SSH密钥之后所有基于SSH的Git操作都可以免密进行。生成密钥对的方式如下这里我以主流的Ed25519算法为例ssh-keygen -t ed25519 -C your.emailexample.com执行之后会询问保存路径直接回车使用默认路径~/.ssh/id_ed25519即可。然后设置一个passphrase这一步可以留空但建议设置一个简单的密码短语防止私钥文件被复制后泄露。生成的密钥对里.pub后缀的是公钥文件可以放心地配置到远端没有后缀的是私钥文件绝对不能泄露给别人。接下来把一个文件内容复制到你的代码托管平台比如Gitee、GitHub。Windows下执行cat ~/.ssh/id_ed25519.pub然后全选复制输出内容。在Gitee上进入“设置”→“安全设置”→“SSH公钥”把你复制的公钥粘贴进去标题随便填一个方便识别的名字比如“work-laptop”。验证是否配置成功运行ssh -T gitgitee.com如果看到提示你以某个用户名认证成功说明整个链路已经通了。此时再执行git clone、git push就不需要再输密码了。2.4 配置Gitee密钥时的几个常见误区配置SSH密钥这一步我在实际指导同事时见过好几个典型的坑这里集中说一下。第一个常见问题是没有把公钥复制完整。ssh-rsa或者ssh-ed25519开头直到邮箱结尾的整段内容是一体的而且公钥通常只有一行复制的时候要确保没有任何换行被截断也不要手动加空格。有些终端在自动换行显示时会让用户误以为多了一行粘贴之后校验失败这个时候用记事本打开文件检查一下具体内容最保险。第二个常见问题是生成密钥时指定了非默认文件名比如ssh-keygen -f mykey生成之后SSh连接时不会自动使用这个私钥。要么改用默认文件名要么在~/.ssh/config里为对应的Host增加IdentityFile配置。第三个问题是配置了公钥之后依然需要输密码。这种情况多半是你clone仓库时用的是HTTPS地址而不是SSH地址。Gitee和GitHub都支持两种协议clone时URL的前缀是https://还是git直接决定了认证方式的区别。想要免密必须使用gitgitee.com:开头的SSH地址。3. 核心日常操作详解3.1 创建仓库与第一次提交把代码纳入版本管理有两种常见路径。一种是本地项目还没有Git仓库在项目根目录执行git init执行完目录下会生成一个隐藏的.git文件夹这就是你的本地仓库。不要小看这个细节很多人会把.git文件夹和项目代码一起打包备份或者误删掉这些操作都会让版本历史彻底丢失。另一种是从远程仓库克隆已有项目git clone gitgitee.com:username/repository.git克隆下来的项目已经自带完整的Git历史信息不需要再执行git init。初始化之后第一次提交的流程是这样git add . git commit -m init project这里有一个很多新手会犯的理解偏差git add并不是把文件“提交”了而是把改动从工作区放到暂存区。Git的设计里有一个中间状态叫暂存区index你可以把它理解成购物车——先把想买的东西一件件放进去最后统一结账生成一个提交。这样做的意义在于你可以把一次逻辑改动的多个文件分组提交而不是把所有文件都打成一个包含混乱目的的提交。3.2 文件状态工作区、暂存区、版本库搞清楚一个文件在Git里处于什么状态是理解一切后续操作的基础。这也是我反复跟团队新人强调的知识点。Git里一个文件可能的状态包括未跟踪Untracked、已修改Modified、已暂存Staged、已提交Committed。用git status命令可以看到当前仓库的完整状态。未跟踪文件在目录里但Git还没有管过它。刚git init之后的新文件或者新建但从未add过的文件都属于这个状态。已修改文件被Git跟踪过但自上次提交以来你又改了内容。已暂存你执行了git add文件改动已经放进暂存区但还没提交。已提交你执行了git commit改动已经生成一个commit记录成为历史的一部分。理解这个状态流转对后面讲撤销操作至关重要。比如git restore和git reset都可以撤销改动但作用的目标完全不同——前者作用于工作区的文件内容后者作用于暂存区的索引状态。3.3 查看历史与文件差异写代码的过程中经常需要回答两个问题“之前发生了什么改动”和“这次到底改了什么”。查看提交历史用git loggit log --oneline --graph --all--oneline让每次提交只显示一行摘要--graph用字符画的方式显示出分支的合并路径--all把各个分支的历史都显示出来。这是我认为信息密度最高的日志查看方式建议配置成别名。查看工作区某个文件相对最近一次提交的改动用git diffgit diff src/App.vue不加参数时git diff只显示尚未暂存的改动。如果你已经git add了想看看暂存区和上个提交的差异要用git diff --cached这两个命令的差别是我在带新人时经常强调的。git diff比较的是工作区和暂存区git diff --cached比较的是暂存区和版本库。搞混它们的后果就是你想看自己一次性改了多少内容结果输出是空的你以为没改实际上改动已经在暂存区里了。3.4 提交信息规范为什么不能写“update”提交信息是Git历史里除了代码本身之外最有价值的文本资产。我见过有些仓库的提交记录满屏都是“update”和“fix”过两三个月再看根本搞不清楚某一次改动当时是为了解决什么问题。我自己在团队里推行的提交信息格式是几个固定前缀加描述feat:新增功能fix:修复bugrefactor:重构不改变外部行为docs:文档相关test:测试相关chore:构建任务、工具链等杂项perf:性能优化比如fix: 修复登录页在Safari浏览器下按钮无法点击的问题这条信息本身就足以让人在排查问题时快速锁定范围。这属于工程规范的范畴它不是强制命令但是一个值得养成的习惯。顺便说一个小技巧如果你提交完之后发现提交信息打错了字但提交内容没问题可以用git commit --amend进入编辑器修改最近一条提交信息。它会把暂存区的改动和最近一次提交合并生成一个新的提交替换掉原来的那个。这个命令很实用但要注意一旦这个提交已经push到远端并且其他同事已经pull了就不要再amend了否则会造成历史分叉别人下次pull的时候会引发一堆冲突。4. 撤销、回退与历史改写4.1 文件还没提交想撤销怎么办这是使用频率最高的撤销场景你改了一个文件回去一看发现改错了想恢复到上次提交时的状态。根据改动是否已经git add有两种情况情况一改动只在工作区还没执行addgit restore src/component.js这个命令会把工作区的文件恢复到最近一次提交的状态。需要注意的是git restore是Git 2.23版本引入的命令对于更早的版本对应的旧写法是git checkout -- src/component.js功能完全一致。情况二改动已经add进了暂存区你想取消暂存但保留文件内容的修改git restore --staged src/component.js这个操作是把文件从暂存区移出回到未暂存状态工作区的内容还是改过的。之后再执行git restore工作区的改动才会被丢弃。这两个命令的区别一定要牢牢记住。我见过有人因为混淆这两个操作把还没提交的代码直接覆盖丢了。所以操作前用git status确认一下文件当前的状态是成本最低的保险。4.2 已经提交了commit但发现有bug提交之后发现代码有问题这是每个开发者都会遇到的事情。处理方式取决于你想回到过去到什么程度。如果你只是想修复问题重新提交一个新的commit即可不需要运行任何回退命令。提交历史是连续增加的新提交指向旧提交作为父提交这是最自然的方式。如果你想反悔这一次提交让代码回到这次提交之前的样子有两个命令可以用git reset和git revert。用一个表格来对照两者的区别场景推荐命令说明本地提交还没push想撤销git reset简洁且可以配合不同参数选择保留或丢弃改动已经push到远端统共同步git revert生成一个新的反向提交不会改写已有历史想彻底删除最近一个commitgit reset --hard HEAD~1危险操作会连同工作区改动一起丢弃git reset有--soft、--mixed、--hard三个模式它们控制重置时对暂存区和工作区的影响--soft只移动HEAD指针改动全部保留在暂存区。--mixed默认移动HEAD指针重置暂存区但保留工作区改动。--hard移动HEAD指针重置暂存区同时丢弃工作区所有改动。git reset --hard是我最谨慎使用的命令之一因为它会直接丢弃未提交的改动而且无法恢复。如果只是需要回到过去并且保留改动继续修改--mixed模式更安全。git revert则完全另一种思路它不动历史的提交记录而是生成一个反向提交。比如你提交了A运行git revert A之后Git会计算A的改动并生成一个与之相反的提交代码效果上等同于撤销了A但历史里既有A也有撤销A的新commit。这种方式对团队协作更友好因为已经push的历史没有被改写其他成员pull时不会出现历史冲突。4.3 commit --amend的完整使用指南热词里专门有git commit --amend这里展开讲一下这个高频但容易被误用的命令。git commit --amend的字面意思是“修订提交”它的功能是把当前暂存区的改动合并到最近一次提交里同时也可以修改这次提交的提交信息。最常见的用法是补漏git add . git commit --amend当你提交后立刻发现有文件忘加了或者提交信息写错了都可以用这个命令解决。执行git commit --amend后会打开编辑器让你修改提交信息。如果提交信息不用改可以直接加上--no-edit参数跳过编辑直接复用旧信息git commit --amend --no-edit这里要郑重提醒一点git commit --amend实质上是创建了一个全新的提交对象。原本的提交记录虽然看起来还在但实际上已经没有任何分支引用它了会被Git的垃圾回收机制在后续某个时间点清理掉。因此千万不要对一个已经push到远端并且别人已经拉取了的提交执行amend操作。如果你确实需要修改一个已经被多人共享的提交正确的姿势是用git rebase -i结合reword或fixup然后强制推送但这属于团队协作中需要谨慎协调的流程不是单机操作的范畴。4.4 误删分支如何找回分支删除之后如果有commit没有合并到其他分支它们并不会立即被物理删除只是失去了引用。借助git reflog你可以在一定时间内找回它们。git reflog这个命令会输出一段“仓库活动记录”包括每一次HEAD移动、分支切换、重置等操作的对象ID。找到你删除分支之前的那个commit ID然后执行git branch recover-branch commit-id这样就基于那个commit重新创建了一个分支。reflog记录默认保留90天所以只要不是被清理过找回误删分支的成功率很高。这个技巧作为急救手段非常好用但别把它当成常规操作的保护伞。日常开发中删除分支前最好用git branch -d而不是-D。-d会检查分支是否已完全合并没有合并时它会拒绝删除等于多加了一层保护。5. 分支管理与团队协作5.1 分支的本质一个会移动的指针很多新手对分支的理解容易走偏以为分支就是代码的一整套拷贝。实际上Git里的分支本质上只是一个指向某个提交对象的指针是一个轻量的存在。创建分支git branch feature/login切换分支git checkout feature/loginGit 2.23之后推荐用更语义化的写法git switch feature/login创建并且切换一步完成git checkout -b feature/login # 或者 git switch -c feature/login创建分支本身不拷贝任何文件只是新增一个指针指向当前HEAD所在的提交。正因为它如此轻量Git才鼓励开发中多用分支——开一个分支实验一个想法不行就删掉几乎零成本。这种模式比SVN时代“在主干上小心翼翼修改”的体验好了太多。5.2 合并分支merge与rebase的取舍分支开发完毕需要把改动合并回主线。Git提供了两种合并思路git merge和git rebase。git merge会产生一个合并提交Merge Commit它把两条分支的历史拼接在一起。好处是完整保留了分支的合并痕迹缺点是需要查看历史时git log --graph会看到分叉再合并的复杂路径。对于多人频繁提交的项目这种图读起来比较费劲。git rebase的思路是把当前分支提交的基底“平移”到目标分支的顶端让提交历史成一条直线。好处是历史非常整洁、线性排查问题容易。坏处是rebase会改写提交的哈希值如果这个分支已经发布并被其他人使用rebae会给团队协作制造混乱。在实际团队里我的经验是私有分支本地开发尚未推送大胆使用rebase整理历史。公共分支已经推送多人协作永远只用merge。具体操作示例。在功能分支上执行# 先切回主分支拉取最新 git switch main git pull origin main # 切回功能分支rebase到main上 git switch feature/login git rebase main执行过程中如果遇到冲突Git会停下来让你解决。解决完冲突之后git add . git rebase --continue如果某一步发现处理不下去了可以执行git rebase --abort彻底放弃这次rebase回到执行前的状态。这条命令相当于给rebase装了安全气囊。5.3 冲突解决的完整流程演示冲突是Git使用中最让人头疼但又完全无法回避的问题。当一个文件的同一处区域被两条分支以不同方式修改时Git无法自动判断哪个是正确版本只能交给人工处理。模拟一个冲突场景假设README.md第1行在main分支上被改成“项目说明”在feature/search分支上被改成“搜索功能项目”两个分支合并时这个文件的第1行就会发生冲突。冲突发生时git merge会停下来并且被冲突文件的内容会变成类似这样的形式 HEAD 项目说明 搜索功能项目 feature/search HEAD到之间是当前分支的版本到之间是要合入分支的版本。你需要手动编辑这个文件删除冲突标记把内容改成你真正想要的结果。修改完之后执行git add README.md git commit注意合并冲突时的提交不需要写-m参数因为Git已经帮你预填了一个merge的提交信息。直接打开编辑器确认内容即可。关于冲突处理几个经验可以分享。一是尽量用编辑器或IDE的冲突处理界面比如VS Code会提供“Accept Current Change”“Accept Incoming Change”“Accept Both Changes”三个按钮比手动删除标记更直观。二是冲突文件越早解决越好尽量别拖太久因为你在半路上如果切换分支或者执行其他操作会引发更多混乱。三是遇到不合理的冲突比如整个文件被大范围替换先跟改动相关的人沟通别自己闷头处理完直接提交容易埋下隐患。5.4 与远程仓库的协作push与pull本地开发完把分支推送到远程git push origin feature/login第一次推送新分支时Git可能会提示你设置上游跟踪关系git push -u origin feature/login-u参数的作用是把本地的feature/login分支与远程的feature/login分支关联起来之后直接敲git push和git pull不需要再指定分支名。拉取远程更新git pullgit pull实际上是两步操作的组合先执行git fetch把远程的更新拉到本地仓库再执行git merge把它合并到当前分支。如果不希望自动合并可以先手动fetch查看差异之后决定怎么处理git fetch origin git log main..origin/main git diff main origin/main先用git log看一下远程多出来的提交再用git diff看具体差异确认没问题再merge。这种“先看后合”的习惯在公共分支上尤其受用。直接git pull当然简单但遇到拉下来代码把本地环境跑挂了的情况就会怀念刚才那十几秒钟的判断。6. 实用技巧与常见问题排查6.1 .gitignore的正确写法每个项目里都应该有一个.gitignore文件告诉Git哪些文件不纳入版本管理。常见的需要忽略的文件包括编译产物比如node_modules/、dist/、build/、target/环境配置文件比如.env、.env.local系统文件比如.DS_Store、Thumbs.dbIDE配置比如.idea/、.vscode/如果是团队统一配置可以例外日志文件比如*.log一个典型的Node.js项目的.gitignore长这样node_modules/ dist/ .env *.log .DS_Store.gitignore匹配规则的细节偶尔会让人困惑最关键的一点是它只影响未被跟踪的文件。如果一个文件已经被git add过甚至提交了再去修改.gitignore加规则是没有用的那个文件依然会被跟踪。正确的处理方式是先从Git中移除跟踪再添加忽略规则git rm --cached .env--cached参数可以把文件从Git索引中移除但保留工作区的实际文件恰好适合处理“之前不小心提交了不该提交的文件”这种场景。6.2 stash暂存工作区改动的救命工具你正在feature分支开发新功能改到一半突然需要紧急修复另一个分支的问题。此时如果切换分支Git会因为当前工作区有未提交的改动而拒绝切换或者把改动带到目标分支去搞混代码。此时git stash就是解决这个问题的工具git stash它的作用是把你工作区里未提交的改动暂存起来让目录恢复到干净状态。之后你可以放心地切换分支处理其他事情。等处理完切回原分支恢复改动git stash pop如果需要暂存多个不同的改动集可以给stash命名git stash save wip: login feature git stash list用git stash list可以看到所有暂存的记录。恢复特定的一条git stash apply stash{2}pop和apply的区别在于pop会恢复改动并删除对应的stash记录apply会保留记录。如果你用apply记得之后手动清理不再需要的stash否则积攒下来的stash列表会非常难维护。6.3 常见问题速查表把日常开发中会反复遇到的高频问题整理成一张速查表方便随时翻。问题场景解决方案改乱了工作区想恢复文件git restore 文件名add错了文件想撤出暂存区git restore --staged 文件名提交信息写错了git commit --amend想撤销最近一次本地提交但保留改动git reset --soft HEAD~1想撤销本地提交并且彻底丢弃改动git reset --hard HEAD~1已推送的提交想撤销git revert 提交ID分支开发到一半要救急git stash处理完git stash pop误删了分支想找回git reflog找到提交ID再git branch 新分支名 提交ID拉取远程更新后发现冲突手动编辑冲突文件后git add再git commit提交了不该提交的敏感文件.gitignore添加规则然后git rm --cached 文件名当前分支落后于远程不想merge产生分叉git rebase origin/main想查看最近改了哪些文件git log --name-only想知道某一行是谁在什么时候加的git blame 文件名6.4 实操心得让Git融入工作流Git用得好的团队通常不只是把Git当成代码备份工具而是把它融进整个工作流程。这里分享几个我摸索出来的习惯每个都能在关键时刻省下不少时间和精力。第一频繁提交小步走。不要等一个功能全部写完再提交而是每当一个逻辑单元完成时提交一次。比如“实现登录表单校验”就是一个合理的提交粒度而“把登录、注册、密码重置三个功能全做完了再一起提交”就太粗糙了。小步提交的好处是哪天功能做坏了你回退的范围小代码review起来也轻松。第二推送前先检查diff。在执行git push之前用git diff和git status确认一下自己将要推送的内容。这个动作一开始觉得繁琐但养成习惯之后能避免把调试用的临时代码、忘了删除的console.log推送到远端。第三提交后立即处理amend。如果你的提交只是本地状态发现问题立刻git commit --amend处理掉别拖到后面成为复杂操作。反过来一旦推送出去就放弃修改这条提交的念头有问题就新增一个修复提交。第四用别名缩短高频命令。推荐几个我常用的别名配置git config --global alias.st status git config --global alias.co checkout git config --global alias.br branch git config --global alias.ci commit git config --global alias.lg log --oneline --graph --all --decorate配置完git lg就能以一行一图的方式展示完整的分支历史这是我日常用得最多的命令。回到题目里那个热门问题“git commit --amend怎么使用”——其实amend只是一个引子它背后的核心是理解Git的提交对象机制以及历史改写的边界。掌握了提交、分支、撤销、合并这套完整的能力之后你自然就能判断什么时候该用amend、什么时候该用reset、什么时候该乖乖再提一个commit修复。Git的使用没有那么多玄学核心在于搞清楚每个命令操作的对象是工作区、暂存区还是版本库剩下的就是反复刻意练习把常用操作变成肌肉记忆。
