很多人学Git,其实是卡在“命令太多记不住”这个坎上。我自己刚开始也是这样,每天打开终端就对着一个git help发呆,今天提交忘了加文件、明天分支合并变出一堆冲突,最后干脆回到老办法:把代码压缩包改名加日期。直到后来被一次事故教育了——本地没提交的代码被误删,找回来成本极高,才老老实实把Git从头到尾系统过了一遍。
这篇笔记原本是我整理给自己看的,按“理解模型 → 搭好环境 → 日常高频操作 → 协作场景 → 疑难杂症”五条线梳理,现在把它完整放出来。适用对象包括:刚装好Git不知道第一步干什么的新手、用了一段时间但每次遇到分支合并都要百度的初级开发者,以及想把手头Git问题一次性排查干净的老同学。这里没有任何花里胡哨的源码分析,全部是实际敲过的命令和踩过的坑。
1. Git 到底是个什么东西:先搞懂三个区域
1.1 别急着敲命令,先建一个“存档”心智模型
很多教程上来就让你git init、git add、git commit,但你根本不知道这些动作在干嘛。我打个比方:Git 就像你玩那种带存档机制的游戏。工作区是你正在打的这一关,暂存区是一个“准备存档的候选名单”,版本库是真正写入硬盘的存档文件。你在游戏里捡了金币、开了宝箱,如果不进存档点(commit),退出游戏就全没了。
对应到代码里就是:
- 工作区(Working Directory):你肉眼看到的、编辑器里正在编辑的文件。
- 暂存区(Index / Stage):执行
git add之后,文件被放在了“待提交”区域。它像是提交前的一个缓冲,让你可以分批次挑文件提交。 - 版本库(Repository):执行
git commit之后,文件被生成一个 commit 对象,永久记录当前的快照。
这个模型能解释 90% 的日常操作。比如很多人问为什么git diff有时候什么都不显示,就是因为修改还在工作区,没被 add,git diff默认只比较工作区和暂存区;git diff --staged才是比较暂存区和版本库。理解了这三个区域,你看到任何命令都能迅速判断它操作的是哪个层次。
1.2 为什么说 Git 的分布式设计是“买保险”
Git 和 SVN 最大的区别是:SVN 的仓库在中央服务器上,本地永远是“客户”;Git 则把整个仓库历史完整克隆到本地,每一个克隆都是一个完整的仓库。这意味着你离线也能提交、看历史、切分支,断网照样干活。等有网络了再 push,别人也能从你的仓库拉走完整记录。
这个设计还有一个隐含优势:仓库不依赖单一节点,本地就是最可靠的备份。我自己后来在团队里强制推行“必须每天至少 commit 一次,及时 push”,不是不信任同事的代码习惯,而是 Git 分布式的特性让代码在每个人的电脑上都有了一份可恢复的副本。
1.3 理解 Git 的“快照”而不是“补丁”
SVN 记录的是文件差异集合,Git 记录的是每次提交时整个项目的快照。听起来很占空间,但 Git 做了压缩和对象去重,相同的文件不会被重复存储。这也解释了为什么 Git 操作那么快:切分支其实就是切换指针,不是复制文件。
如果你理解了“快照”这个概念,git checkout、git reset、git revert这些命令就不难懂了。它们本质上是“把指针移到某个快照,并选择性地同步工作区内容”。
2. 从零搭好环境:安装、配置与免密登录
2.1 不同系统安装 Git 的正确姿势
Windows 用户建议直接从 Git 官网下载安装包,也可以使用国内镜像加速下载。安装过程基本是“下一步下一步”,但有几个选项值得留意:
- Git Bash 是推荐的命令行环境,安装后会在右键菜单出现,建议使用它而不是老的 CMD 来操作 Git。
- 换行符转换建议保持默认(Checkout Windows-style, commit Unix-style),后续文件出现
\r\n相关怪问题再来逐个排查。 - 勾选“Enable Git Credential Manager”,后面 https 推送不用反复输密码就靠它。
Linux 和 macOS 用户走包管理器即可。macOS 如果装过 Xcode Command Line Tools,系统可能自带 Git,但版本偏老,推荐用 Homebrew 装新版本。验证是否成功,终端执行git --version,能看到版本号就说明基本安装到位了。有个容易被忽略的细节是:Windows 装完后要重新启动一次终端,否则环境变量可能没刷新。
2.2 第一次提交前的三件套配置
新机器上第一次 commit 之前,必须先告诉 Git 你是谁。很多人跳过了这一步,结果提交记录里显示的是一个奇怪的“你的名字随机串”,后面再想改历史非常麻烦。执行命令时把邮箱改成你自己的真实邮箱:
git config --global user.name "你的名字" git config --global user.email "你的邮箱"--global表示全局生效,如果某个项目需要不同身份(比如工作项目和私人项目),去掉--global在项目目录里再设置一次即可。配置文件位置在用户当前目录下的.gitconfig,你可以直接用文本编辑器打开检查。
这里我强烈建议花两分钟设置默认分支名和常用别名:
git config --global init.defaultBranch main git config --global alias.lg "log --oneline --graph --all" git config --global alias.st status git config --global alias.cm "commit -m"别看别名是小事,实际用起来能省掉大量重复输入。git lg是我每天用最频繁的命令,可以直观看到分支提交拓扑。
2.3 SSH 密钥配置:实现免密的“正经方式”
如果你用 https 协议 clone 仓库,每次 push/pull 都要输账号密码,虽然 Credential Manager 能记住,但在一些服务器环境里并没有这个组件。更常见的做法是配 SSH 密钥,一次生成,长期免密。
生成密钥的命令,注意-C后面的邮箱只是备注,不影响密钥本身:
ssh-keygen -t ed25519 -C "你的邮箱"默认一路回车即可,密钥会生成在~/.ssh/下:id_ed25519是私钥,绝对不能泄露;id_ed25519.pub是公钥,需要填到代码托管平台。以 Gitee 为例,登录后在“设置 → SSH 公钥”里粘贴.pub文件内容。GitHub 在“Settings → SSH and GPG keys”里操作,路径类似。
绑定完成后测试一下:
ssh -T git@gitee.com如果能收到欢迎信息,说明认证成功。此时再用 SSH 协议的仓库地址 clone,push/pull 都不需要密码了。对于多台电脑使用同一个账号的场景,最省事的做法是:每台电脑各自生成一对密钥,把各自的公钥都添加到账号的 SSH 公钥列表里。不建议把同一份私钥复制到多台机器,一旦某台机器被入侵,私钥泄露就全盘暴露。
2.4 远程仓库推送失败:token 与密码的坑
2021 年后 GitHub 已经不允许用账号密码直接推代码,Gitee 等平台也在逐步收紧。遇到remote: Support for password authentication was removed这类提示,解决方式是在平台设置里生成一个 Personal Access Token,把它当作密码填入推拉认证框。Windows 的 Credential Manager 会自动记住这个 token,后续不再询问。
生成的 token 要妥善保存,平台通常只展示一次。我的习惯是:把 token 直接放进专用密码管理器,而不是随手写在笔记里。
3. 日常高频操作:从提交到回滚的完整闭环
3.1 新项目起步:init 还是 clone
本地从零开始的项目,在项目根目录执行git init,然后git add .把所有文件加入暂存区。再看一眼.gitignore有没有把node_modules、target、.idea这类目录排除掉——这个文件非常重要,我第一次用 Git 时不注意,把 3 万多个依赖文件全提交上去了,仓库瞬间膨胀到几百 MB,每次操作都卡。
已有远程仓库的情况,直接执行:
git clone git@github.com:user/repo.gitclone 之后默认在主干分支(main 或 master),并且本地自动关联了远程名为origin的仓库。对比一下:init 后需要手动git remote add origin 地址才能和远程建立连接。
3.2 提交信息的“及格线”写法
很多团队对提交注释没有要求,但既然这份笔记叫“自用”,我给自己定了一条规矩:每次 commit 信息必须在 50 个字符内说清楚这件事“做了什么”,而不是“改了代码”。比如:
git commit -m "fix: 修复登录页验证码过期后仍可提交的问题"而不是:
git commit -m "update code"推荐采用 Angular 风格的提交前缀:feat表示新功能,fix表示修复,docs表示文档变更,refactor表示重构,test表示测试相关。这样后期用git log --oneline扫一眼,整个版本演进一目了然。一个提交只做一件事,这是个黄金原则。你花十分钟拆分的提交,会让三天后的自己感激不尽。
3.3 commit --amend:改注释和补漏文件
这个命令解决两类问题:一是上一次的提交注释写错了、写得不完整;二是上次提交漏了文件,不想额外产生一个“fix typo”的提交记录。用法非常直观:
# 修改上一次提交的注释 git commit --amend -m "修正后的提交信息" # 补充漏掉的文件到上一次提交 git add 漏掉的文件 git commit --amend --no-edit--amend的底层原理是:放弃当前的 commit,把这个提交的父节点拿出来,合并工作区新内容,生成一个新的 commit。所以它对历史有“改写”效果。这里有一条红线:如果上一次提交已经 push 到远程、且其他人可能已经基于它拉过分支,绝对不要 amend。强行 amend 后会与远程历史冲突,别人 pull 时会变得很痛苦。正确的做法是,已推送的提交就让它留在历史里,再补一个正向的 commit。
另外,如果 commit 的 message 里敲了中文出现乱码,通常是终端编码问题,设置git config --global core.quotepath false后,中文文件名和注释就能正常显示了。
3.4 查看历史记录:log、diff、show 的组合用法
日常最常用的排查组合我整理成了这个顺序:
git status # 先看工作区有什么变化 git diff # 再看未暂存的具体改动 git lg # 然后看历史拓扑 git show <commit-hash> # 某个特定提交改了什么git show默认展示提交说明和 diff 内容,如果只想看某次提交改了哪个文件,加--stat参数。查某一行代码谁改的,用git blame <file>,这个命令在定位“莫名其妙坏掉”的代码时是救命稻草。
3.5 代码写了一半想切分支:stash 大法
场景我不信你没遇到过:正在 dev 分支写功能写了一半,测试突然说线上有个紧急 bug,需要马上切到主分支修。这些写到一半的改动不想提交,但也舍不得丢,怎么办?
git stash # 临时保存当前工作区改动 git checkout main # 安心切分支 # 修完 bug 之后 git checkout dev git stash pop # 恢复之前的工作stash保存的是一个栈结构,可以git stash list查看,也可以多次 stash。唯一要注意的是 pop 时可能会因为新分支有冲突而提示,手动解决冲突后即可。这个命令帮我节省了无数次的“复制半成品代码到临时文件”操作。
4. 分支管理与团队协作:从“一个人写”到“一群人写”
4.1 分支的本质就是一个指针
忘了分支是“代码副本”的想法,它只是一个指向某个 commit 的指针。创建分支的成本极低,所以 Git 的常见协作模型是“每需求一个分支”。日常流程是:从主干切出 feature 分支,开发完成后合并回主干。
git branch dev # 创建 dev 分支 git checkout dev # 切换到 dev git switch dev # 或者这个命令,作用和上面一样 git checkout -b feature/login # 创建并切换一步到位个人更推荐git switch系列:git switch -c 分支名创建并切换,语义清晰,不会像checkout一样负载过重。删除分支用git branch -d,注意如果分支上有未合并的提交会报错,需要大写-D强制删除——慎用。
4.2 从 master 剪切代码到 dev:选择 merge 还是 cherry-pick
有同学问“我在 master 上写了代码,怎样剪切到 dev 分支上”。这个需求分两种情况。如果 master 上的这些代码只是想带到 dev 去继续开发,最简单的是:
git checkout dev git merge master这时 dev 会包含 master 当前的全部提交。但如果 master 上有很多不想带过去的提交,只要其中某个 commit,那就用cherry-pick:
git checkout dev git cherry-pick <commit号>它相当于把这个 commit 在 dev 分支上“重做一遍”。我再强调一遍:cherry-pick会重新生成一个 commit,它和原 commit 的 hash 不同,这很正常,不要试图找“同一个提交被移动过去”的错觉。实测下来,跨分支捡代码的场景,cherry-pick 比 merge 灵活得多,但也要求你清楚自己在捡什么。捡错了可以用git cherry-pick --abort放弃。
4.3 分支合并两种方式:merge 与 rebase 的选择
- merge:生成一个合并提交,历史呈网状,能清晰看到分支从哪里分叉、在哪里汇合。适合团队协作时保留真实开发轨迹。
- rebase:将当前分支的提交“重新基于”目标分支的最新点,历史变成一条直线。适合个人开发时保持提交历史清爽。
我个人的习惯是:功能分支合入主干用 merge,保留完整的合入记录;本地同步 master 最新代码用 rebase,避免污染主干历史。对比一下:
# merge 方式 git checkout main git merge feature/login # rebase 方式:在 feature 分支上执行 git rebase mainrebase 的代价是会改写当前分支的 commit hash,所以和--amend一样,不要对已推送且多人共用的分支执行 rebase。记住这个原则就不会出事。
4.4 解决冲突的经验之谈
合并分支最头疼的是冲突。第一次遇到时我很慌,看到一屏的<<<<<<<、=======、>>>>>>>就想重来。其实处理冲突的正确姿势是:
- 用编辑器搜索冲突标记,逐个文件处理。
- 决定最终保留哪一侧,或者两者都拼接起来。
- 删除所有冲突标记符号。
- 重新
git add这些文件并git commit。
冲突本质上不是技术问题,而是沟通问题。如果是和别人合写同一块代码,务必在合并前问清楚对方意图。我之前吃过亏,想当然地选了我这边的版本,把同事对同一行变量名的结构调整冲掉了,线上直接编译失败。后来团队约定了一个规矩:冲突时保留双方注释、协商解决,杜绝“赢者通吃”。
4.5 撤销的三种场景:Commit 本地、Commit 已推送、未提交
撤销是使用频率最高、也是最容易记混的操作。按对象分三种:
- 没有提交,只想放弃修改:
git checkout -- <file>或git restore <file>,把工作区文件恢复到暂存区/HEAD 的样子。 - 提交了但还没推送:
git reset --soft HEAD~1撤销 commit 但保留暂存区;git reset --hard HEAD~1连改动一起扔掉。注意--hard很危险,会丢代码,除非你现在一个角落里还有备份,否则不要轻易用。 - 已经推送到远程分支:有两条路。一条是
git revert <commit>生成一个反向提交,安全、不改写历史,推荐在团队协作时使用。另一条是git reset --hard <commit>后git push --force,强制覆盖远程历史,这是危险操作,必须确认分支只有你自己在开发才能用。我当时在 IDEA 里误操作过一次 force push,把同事的两次提交冲没了,最后花了一上午从其他人本地仓库找回来,教训极其深刻。
如果是用 IDEA 操作,右键项目“Git → Show History”里能选中历史提交,选择“Revert Commit”也能达到同样效果。但无论什么工具,原理不变:本地已推送且多人协作,优先 revert;一个人独享分支,才考虑 reset + force push。
4.6 子模块 submodule:把仓库嵌进仓库
项目里需要引入另一个 Git 仓库作为依赖,直接用git submodule add <仓库地址>,然后git submodule update --init --recursive初始化。需要注意,submodule 在父仓库里记录的是它指向的具体 commit,不是“最新版”。所以父仓库更新时,子模块不一定同步更新,需要手动在子模块目录里拉取。
说实话,submodule 的坑比收益多。最典型的问题是子模块切换分支后,父仓库会显示“modified content”。如果团队没有约定,尽量用包管理器管理依赖,submodule 仅用于那些非要源码级引入的场景。
5. 疑难杂症排查:我整理的高频问题速查表
5.1 SSH 认证失败:“Permission denied (publickey)”
这是拉取代码时最经典的报错。排查步骤按优先级排序:
ssh -T git@gitee.com或ssh -T git@github.com看是否返回欢迎语。如果提示 permission denied,说明公钥没配对。ls -al ~/.ssh/检查公钥是否存在。- 在平台设置里确认公钥内容与本地
.pub文件一致,注意粘贴时不要多空格、少换行。 - 如果新换过电脑,检查
~/.ssh/config是否有旧配置,或者系统里是否存在多个密钥文件。
一个隐蔽的问题是公司 IT 的代理或防火墙拦截了 22 端口。GitHub 提供 443 端口的 ssh 访问,可以在~/.ssh/config里加一行Host github.com+HostName ssh.github.com+Port 443绕过。Gitee 也类似,需要时直接查对应文档即可。
5.2 git open /dev/null or dup failed: No such file or directory
这个报错不算大众,但搜到的解释太少了。我实测的经历是:这个错误的触发场景与标准输入输出流重定向相关,常见于 Git Bash 环境在某些杀毒软件、终端工具或权限受限的目录下运行时,无法正常给子进程分配文件描述符。
排查顺序:先关闭所有占用 Git Bash 的第三方终端(如某些 IDE 内置终端),改用官方 Git Bash 执行;再检查项目路径是否有中文和空格,我遇到过一次 Windows 路径带中文导致系统调用失败;最后看杀毒软件是否拦截了 Git 的临时文件目录权限。这个报错几乎不影响 Git 本身的数据完整性,重点是把程序运行环境弄干净。
5.3 遇到 .git 目录泄露怎么处理
网上有个热词“git 目录泄露如何下载”,这其实是一个安全话题。如果有人把仓库的.git目录直接暴露在 web 站点上,访问者可能通过特殊请求读取提交历史、源码和敏感配置。对于开发者来说,正确的关注点应该是:
- 部署生产环境时,确认站点根目录不包含
.git目录,或者配置规则禁止访问.git路径。 - 代码托管平台的私有仓库不是“安全仓库”,真正的密钥(数据库密码、token)绝不能提交进任何仓库。
- 如果发现自己的仓库已经泄露,第一件事是马上把泄露的密钥视为“已泄露”,去平台撤销并重新生成,而不是仅仅删除历史提交。
这个话题的教训是:.git 目录是仓库的心脏,它不应该出现在任何对外服务的目录里。前端项目构建时默认会把整个项目拷进 dist,构建产物过滤.git是必做配置。
5.4 高频问题速查表
| 问题 | 典型原因 | 解决命令/操作 |
|---|---|---|
git pull提示“refusing to merge unrelated histories” | 两仓库没有共同祖先 | git pull origin main --allow-unrelated-histories |
中文文件名显示为\346... | 未设置 quotepath | git config --global core.quotepath false |
| Windows 下代码行尾变化导致 diff 巨大 | 换行符 CRLF 被转换 | 统一配置core.autocrlf,团队约定为 true 或 false |
| push 时要求输入 token 而不是密码 | 平台取消密码认证 | 生成 Personal Access Token 并使用 |
command not found: git | PATH 未配置或未安装 | 重新安装/重启终端/macOS 用 Homebrew 安装 |
fatal: refusing to merge unrelated histories变体 | 本地仓库与远程仓库历史独立 | 首次合并用--allow-unrelated-histories |
| 修改一个文件后 diff 没显示 | 没 add,diff 默认对比暂存区 | git diff HEAD查看未暂存改动 |
5.5 常见 GUI 和 IDE 集成工具评点
命令行是基础,但团队里的同学更多时候是在 IDE 里操作。我用过四类工具,简单说说感受:
- Git GUI:Git 自带的简易图形界面,功能极简,适合看 commit 关系图,实际操作不是很有优势。
- TortoiseGit(小乌龟):Windows 经典工具,右键菜单集成,适合不习惯命令行的同事。优点是直观,缺点是功能相对有限,遇到复杂 rebase 容易懵。
- IDEA 内置 Git:功能最完整的 IDE 集成之一,支持从历史里查看 diff、cherry-pick、交互式 rebase。热词里的“idea 修改 git 提交的账户”“idea 撤销远程分支代码”都能在 VCS 菜单下完成。最常见的问题是提交账户不对,检查 IDEA 的 Settings → Version Control → Git 里有没有勾选“Use credential helper”,以及在全局 git config 中 user.name/email 是否正确。
- VS Code 内置 Git:轻量好用,日常 status、diff、commit 完全够用。胜在流畅,适合小型项目,复杂合并建议还是用 IDE 或命令行完成。
我自己的习惯是:日常写代码的机器用 IDEA 内置 Git 看 diff、提交,遇到需要处理历史或复杂冲突时切到命令行,两者各管一段。工具选哪个不重要,核心是把 Git 的模型吃透,换任何工具都不慌。
写在最后:一个折腾Git多年的人的真实体会
来回踩了这么多坑,最大的体会其实只有一个:Git 不需要“背命令”,需要“建模型”。当你脑子里清楚快照、指针、分支这些概念,再回头看commit、merge、rebase、reset,每个命令都只是“对某个对象执行某种操作”。很多报错能凭借模型推理出原因,根本不用查文档。
这份笔记里写的每一条命令我都在真实项目里验证过。如果你按着流程装好了环境,跑通了第一次提交,建议立刻做两件事:一是把三条最常用的命令改成别名,二是打开终端敲一遍git lg看看自己的提交历史。等你哪天不用查命令就能完成分支合并和代码回滚了,说明 Git 这门手艺已经真正长在你身上了。
最后再分享一个小技巧:给自己定一条“下班前必须把工作区清零”的规矩,要么提交、要么 stash,保证工作区永远干净。这不仅防止代码丢失,更重要的是,第二天回来打开电脑时,你永远可以瞬间进入状态,而不必对着一堆改了一半的文件发呆。