做开发这些年,Git 几乎是每天都会用到的工具。从个人项目的版本控制,到多人协作的发布流程,几乎所有代码的变更记录都离不开它。很多朋友刚开始学 Git 时,会觉得命令又多又抽象,工作区、暂存区、本地仓库、远程仓库、分支、HEAD,每个词都能把人绕晕。我也是一路从“只敢用图形界面点点点”走到“命令行随手敲”的,踩过不少坑,所以想把这些年的 Git 实战笔记完整整理出来。这篇内容会从安装配置讲到分支合并、从远程协作讲到疑难杂症排查,尽量用大白话把原理和实操串起来。它适合刚接触 Git 不久的朋友,也适合那些用了一段时间、却总在同样的报错里打转的老手。Git 这套工具,学的时候感觉零零散散,真正吃透之后,你回头看会发现自己对工程协作的理解也顺带升了一级。
1. 为什么是 Git,以及它的核心思路
1.1 Git 和 SVN 到底差在哪
网上关于 Git 和 SVN 的对比文章一搜一大把,但真正让我理解二者差异的,是一个很直观的场景:出差、断网、电脑没插网线。如果用 SVN,当你不在办公室、连不上中央服务器的时候,很多操作是没法做的,不管是看历史还是创建分支,都得依赖那台中心服务器。Git 就不一样,每个开发者的本地目录就是一个完整的仓库,完整的历史记录、全部分支都在本地存着,断网了照样可以提交、可以看历史、可以开分支,等网络恢复后再同步到远程。
这背后是两类版本控制系统的设计差异。SVN 是集中式版本控制,所有版本数据都放在唯一的中央服务器上,客户端只是“取文件、改文件、传文件”。Git 是分布式版本控制,每个克隆下来的仓库都包含全部历史记录,任何人都等于拥有一份完整备份。这意味着两个实打实的好处:一是容灾能力极强,任何一个开发者的本地仓库都能恢复出整个项目;二是提交是本地行为,速度快,不需要每次操作都跑到服务器上绕一趟。
从使用习惯上看,SVN 的目录组织比较“有层级感”,通常会在仓库根目录下分 trunk、branches、tags,而 Git 的分支更像是一个个轻量指针,灵活度高出不少。分支的创建和切换在 Git 里几乎是瞬间完成的操作,不需要在服务器上复制一份完整目录。这也是现在团队协作里 Git 明显占优的原因——分支便宜、合并顺手、历史记录可靠。
1.2 理解 Git 的三棵树与“快照”思维
第一次认真看 Git 教程时,我被“暂存区”这个词卡了很久。后来我用一个类比搞懂了:Git 把每一次提交都看成一个“快照”。就像玩游戏时存进度——你在某个时间点把整个游戏世界当前的状态拍了一张照,存下来编号。之后想回到这个编号,就能直接恢复到这个状态。所谓版本管理,本质就是管理这一堆“快照”之间的关系。
在这个基础上,理解“三棵树”就顺理成章了。Git 在日常操作中维护着三个主要区域:工作区是电脑上看到的那些文件和文件夹,是你真正编辑代码的地方;暂存区可以理解成“待打包清单”,git add 就是把某个文件的当前状态加入清单;本地仓库就是 git commit 之后,暂存区的内容被打成一个快照,永久写入版本历史。这里的 HEAD 可以简单理解成“当前所在的分支”,它决定接下来新提交会挂在哪个分支上。
很多人刚上手时不理解为什么有暂存区这个中间层,直接“提交全部”不就行了。暂存区真正的作用,是让你能精细控制“这一次提交包含哪些文件的哪些改动”。比如你改了五个文件,其中三个是修复 bug 的、两个是同一个新功能的,你会希望把它们拆成两个逻辑清晰的提交,这样后面查历史、回滚版本都更有条理。养成“一个提交只做一件事”的习惯之后,代码 review 的体验也会好很多。
1.3 HEAD、分支与引用,没那么神乎其神
HEAD 也是让新手困惑的概念。最简单的理解:HEAD 是一个指针,指向当前所在的分支;分支本质上也是指针,指向某一次提交。当你执行 git switch main 时,HEAD 就指向 main 这个分支。执行 git commit 时,Git 会把新提交挂在当前分支指针的后面,再让分支指针前进到新提交。
在 Git 内部,分支其实就是一个引用文件,里面存着一个哈希值,指向某个提交对象。理解了这一点,你就明白为什么 Git 上创建分支是“秒级”的——它本质上就是写一个小文件。这也解释了为什么鼓励多开分支、多提交,因为分支便宜,实验成本低。我在实际工作中的习惯是:任何新功能、新实验,都先拉一个分支,哪怕只是临时验证,也不要在 main 分支上直接乱改。真出了问题,删掉分支重来,不会有任何心理负担。
2. 从零安装到配置,先把手里的工具调顺
2.1 跨平台安装 Git:Windows / Ubuntu / macOS
先说 Windows 平台。最常见的方式是从 Git 官网下载安装包,界面很直观,一路 Next 就能装完,但有几个关键选项必须留神,这也是很多人装完以后用不了的原因。
| 安装选项 | 建议选择 | 原因 |
|---|---|---|
| Select Components | 勾选 Git Bash Here 和 Git GUI Here | 右键菜单方便,日常操作省事 |
| Default editor | 建议选 VSCode 或 Notepad++ | 默认的 Vim 对新手不友好,写多行提交信息时会很难受 |
| Adjusting PATH | 选 Git from the command line and also from 3rd-party software | 保证终端里能直接用 git 命令 |
| Line ending conversions | 选 Checkout as-is, commit as-is | 避免自动转换行尾带来的无意义 diff |
| Enable file system caching | 勾选 | 提升大仓库的响应性能 |
国内下载境外服务器上的安装包时,速度往往不太理想,这种情况可以借助国内开源镜像站提供的 Git for Windows 安装包,一样安全可靠。如果不想用安装包,Windows 上也可以用 winget 一条命令装好:winget install --id Git.Git -e --source winget。装完以后,记得打开一个新的 CMD 或 PowerShell 窗口验证一下,输入 git --version,能看到类似 git version 2.47.1.windows.1 的输出就说明成功了。这一步非常关键,因为环境变量是窗口启动时读取的,旧窗口里大概率还是识别不了 git。
Ubuntu / Debian 系统就简单了,执行 sudo apt update && sudo apt install git -y。macOS 用户可以安装 Xcode Command Line Tools,命令是 xcode-select --install,也可以直接用 Homebrew 执行 brew install git。装完统一验证 git --version。不同系统出来的 Git 版本会有差异,这不用纠结,只要别太老就行。我自己在 Ubuntu 上经常用 apt 默认版本,只有需要某些新特性时才会单独换官方发布的更新版本。
2.2 全局配置与 SSH 密钥
装好后第一件事不是急着建仓库,而是配置身份。Git 需要在每次提交时知道“这个提交是谁做的”:
git config --global user.name "你的名字" git config --global user.email "your_email@example.com"这里提醒一下,user.email 建议和远程仓库(Gitee、GitHub、GitLab 等)注册邮箱保持一致,这样提交记录才能和账号关联上,否则首页上的贡献图可能看不到你的记录。也可以给某个仓库单独设置身份:在仓库目录下执行 git config user.name "xxx",去掉 --global 即可。配置生效的优先级是系统级大于全局大于仓库级,仓库级配置会覆盖全局配置。
接下来是 SSH 密钥。很多人用 Gitee 或 GitHub 时遇到“认证失败”,多半是密钥没配好。生成密钥的命令如下:
ssh-keygen -t ed25519 -C "your_email@example.com"一路回车即可,生成的默认位置是 ~/.ssh/id_ed25519 和 id_ed25519.pub。公钥内容可以安全公开,拿它去配到平台的“SSH 公钥”设置里;私钥绝对不能泄露,也别提交到仓库。Windows 上公钥路径一般在 C:\Users\你的用户名.ssh\id_ed25519.pub,用文本编辑器打开复制即可;macOS / Linux 可以用 cat ~/.ssh/id_ed25519.pub 查看。
配好公钥后,用以下命令验证是否连通:
ssh -T git@gitee.com第一次连接会提示是否信任主机指纹,输入 yes 保存到 known_hosts 就行。如果显示你的用户名并提示认证成功,说明密钥已经生效。很多新手以为生成密钥就完事了,其实后面还有一层 ssh-agent 用来缓存私钥。如果你有多个密钥文件,或者放在非默认路径,就可能需要在 ~/.ssh/config 里指定具体用哪一把私钥,否则 SSH 可能拿着错误的密钥去连接,反复报 Permission denied。
2.3 SSH 认证失败的常规排查
SSH 认证失败是我见过的高频问题,报错通常是 Permission denied (publickey),或者类似 git@gitee.com: Permission denied (publickey)。排查思路从简单到复杂,一般按这个顺序来:
- 检查公钥是否真的添加到了对应的远程仓库。很多人把公钥配到了 A 平台,却用 B 平台的地址去连接,自然会失败。
- 检查本地私钥路径是否正确。默认的 ~/.ssh/id_ed25519 存在吗?生成时如果用了非默认文件名,Git 不会自动识别,需要配置 ~/.ssh/config 来指定 IdentityFile。
- 确认 SSH 代理或系统代理是否干扰。有些内网环境会拦截 22 端口,可以尝试平台的备用 SSH 端口,比如 Gitee 支持通过 ssh.gitee.com 的 443 端口连接。
- 检查 known_hosts 是否有异常记录。如果主机的指纹信息发生了变化,会报 Host key verification failed,删掉 known_hosts 里对应行后再重连即可。
- 在 Windows 上,检查系统自带的 OpenSSH 和 Git 捆绑的 ssh.exe 是否存在版本混用。两个 ssh 可能读取不同的密钥路径,导致 Git 里配置的密钥没有被真正加载。
注意:在 Linux / macOS 上,SSH 私钥文件权限不能太宽松,一般要求 -rw-------,也就是只有当前用户可读写,否则 SSH 会出于安全考虑直接拒绝使用该密钥。
3. 日常提交与分支合并的核心动作
3.1 初始化仓库与首次提交
进入项目目录,执行:
git init -b main新版 Git 默认分支名已经叫 main,用 -b 参数可以直接指定。如果你用的版本比较老,也可以先把默认分支名配置成 main:git config --global init.defaultBranch main。这一步不影响功能,但能让新仓库的分支命名保持一致,省得以后团队里有人用 master 有人用 main,看着都头疼。
初始化之后,马上创建 .gitignore 文件,把 node_modules、target、dist、.idea、*.log 这些不该提交的内容写进去。这个文件越早建越好,别等到误提交一堆依赖包之后再费劲清理。我会把常用的 .gitignore 规则放在自己的模板里,新项目直接复制,省心很多。
接着是第一次提交的经典三连:
git add . git commit -m "init: project setup"git add 的粒度可以很细。git add filename 只添加单个文件;git add . 添加当前目录所有改动;git add -p 可以交互式地按文件内片段选择要暂存的内容,适合做精细提交。提交后用 git status 查看状态,用 git log --oneline --graph -10 看提交历史。这里给新手一个建议:提交信息一定要写清楚“为什么改”,而不是“改了什么”。改了什么看 diff 就能知道,但为什么这样改,才是提交信息应该回答的问题。
3.2 分支的新建、切换与合并
分支是 Git 里最核心的协作单元。新建并切换分支可以用一行命令:
git checkout -b feature/login # 新版 Git 也支持更语义化的写法 git switch -c feature/login切换分支前,养成习惯先看一眼 git status。如果有未提交的改动,贸然切换可能会把这些改动带到另一个分支里,或者需要你先 commit 或 stash 再切。这个坑我踩过不止一次:在 A 分支改了半天的代码,切到 B 分支一看,改动全带过来了,差点把两个分支的逻辑搞混。
合并分支用 git merge:
git switch main git merge feature/login如果 feature 分支是从 main 最新提交点拉出去的,且 main 在这期间没有新提交,那么 merge 会执行 fast-forward,直接把 main 指针前移,历史看起来就是一根直线。如果 main 也有新提交,Git 会自动做三方合并并生成一个 merge commit。如果两边改了同一个文件的同一处地方,就会产生冲突。
为了避免“合并一时爽,冲突火葬场”的体验,我建议在合并非长期维护分支时配合 --no-ff 参数:
git merge --no-ff feature/login这样即使可以 fast-forward,也会强制生成一个 merge commit,让历史更清晰地表达“这里曾经合并过一个完整的功能分支”。
3.3 commit --amend 和交互式 rebase
git commit --amend 是“修改最后一次提交”的命令。常见用法有两种:补充遗漏的文件,或者修改提交信息。比如提交完之后发现忘了加一个文件:
git add forgotten.txt git commit --amend注意,amend 本质上不是修改旧提交,而是用一个新的提交替换掉旧的。旧提交对象其实还在对象数据库里,只是没有任何引用指向它,之后会被垃圾回收。因此,如果你已经把这个提交推送到了远程共享分支,请谨慎使用 amend,别人拉取时会遇到“远端历史与本地历史分叉”的尴尬。对于还没推送的本地提交,amend 完全安全,可以放心用。
如果连续几次提交都不满意,想重新整理多个提交,就需要交互式 rebase:
git rebase -i HEAD~3这会打开一个编辑器,列出最近三个提交。你可以把 pick 改成 reword、squash、fixup、drop 等操作。我最常用的是 squash,把几个“WIP”中间态提交压缩成一个完整的功能提交。rebase 同样有一条铁律:不要对已推送的公共分支进行 rebase,否则等于改写别人已经拉取的历史。个人功能分支上可以放心随便 rebase,推送前把历史整理干净,团队 review 时观感会好很多。
4. 多环境协作:远程仓库与免密配置
4.1 关联远程仓库与拉取流程
在 Gitee 或其它平台上建好空仓库后,本地关联远程:
git remote add origin git@gitee.com:yourname/yourproject.git git push -u origin main-u 的作用是建立“上游”关联,把本地 main 分支和远程 main 分支绑定,之后直接 git push 和 git pull 就能自动找到对应的远程分支。查看远程信息用 git remote -v,会列出 fetch 和 push 对应的地址。如果远程地址写错了,可以用 git remote set-url origin 新地址来修正。
很多人从一开始就习惯用 git pull 拉取。实际上 git pull 是 git fetch + git merge 的组合。对于个人维护的仓库,pull 很省事;但在多人协作时,我更推荐显式地 git fetch 先看一眼远程更新,再决定是 merge 还是 rebase。你也可以设置 pull 的默认行为为 rebase:git config --global pull.rebase true,这样每次 pull 相当于先把本地提交暂时收起来,拉取远端新提交后,再把你的本地提交重新放上去,历史会更干净。
如果是通过 IDE 拉取项目,IDEA 里可以直接 File -> New -> Project from Version Control,粘贴仓库地址;VSCode 则在 Source Control 面板里点击“Clone Repository”,输入地址即可。这些图形化操作本质上还是执行底层 git clone 和 remote 命令,只是把流程封装得更友好。首次推送时如果弹出账号密码窗口,含义就是远程仓库需要验证身份,输对一次后通常由系统凭据管理器记住。
4.2 免密登录的三种方案
用 HTTPS 地址时,每次 push 都要输入账号密码确实烦人。免密配置的本质,就是把凭据交给系统级的凭据管理器来保存和自动填充。Windows 上可以启用 Git Credential Manager,也可以手动设置:
git config --global credential.helper manager之后第一次输入账号密码或通过浏览器完成登录,凭据会被安全存进 Windows 凭据管理器,第二次就不需要再输入。macOS 默认使用 osxkeychain;Linux 上可以用 credential.helper store,但它是明文存储,不推荐;更稳妥的是用 libsecret 或 gnome-keyring。如果你用的是第二个方案,请先确认系统里有没有安装对应依赖,否则配置了也不生效。
SSH 方式本身就是免密的:只要公钥配在平台上、私钥在本地且能被 ssh-agent 找到,git push 就不会再要求输入密码。这也是我推荐使用 SSH 地址的原因。SSH 还能避免 HTTPS 上 token 过期的问题,但代价是需要管理密钥文件,多环境多账号时会稍复杂一些。
4.3 清除或更新本地缓存的账号密码
如果换了平台账号,或者 token 泄露想让它失效,本地缓存的凭据可能还在持续生效。排查方式要看系统和 credential helper 的类型。Windows 上可以打开“控制面板 -> 凭据管理器 -> Windows 凭据”,找到类似 git:https://gitee.com 的条目删掉;命令行也可以:
cmdkey /list cmdkey /delete:git:https://gitee.comLinux 上如果用了 store,直接编辑 ~/.git-credentials 文件;如果用了 cache,就不用管,它过段时间会自动过期。还有一个常见写法是把 token 直接塞进远程 URL 里:
git remote set-url origin https://user:token@git.example.com/repo.git这种写法虽然方便,但非常不安全,token 会出现在 shell 历史和 git remote -v 的输出里,如果被人看到,等于把仓库的写入权限直接交了出去。我自己基本不用这种方案,宁可多用一步配置凭据管理器。
提示:如果只是某一次推送被服务器拒绝,提示认证失败,先不要急着暴力清缓存。先用凭据管理器删掉旧条目,然后重新 push,让它弹出新的认证窗口输入正确凭据,这样最省事。
5. 疑难杂症速查,这些报错我基本都踩过
5.1 “fatal: not a git repository”的排查
这条报错出现时,通常你会一脸懵:我明明就在项目目录里啊?可能的原因就那几类:当前目录本身不是 Git 仓库,也就是没有 .git 目录;工作目录层级不对,Git 只能在仓库内部找到父级 .git;或者 .git 目录被误删、被移动了。还有一种情况是在子模块目录里执行 git 命令,但子模块元数据损坏了。
排查三步走:
- pwd 或 cd,确认当前路径确实在项目目录里。
- ls -a,看看有没有 .git 目录。有的话,用 git rev-parse --show-toplevel 查看仓库根目录,确认 Git 识别到了哪个层级。
- 如果 .git 目录真的没了,但你还记得上次提交的哈希,可以尝试 git fsck --lost-found 找回尚未被垃圾回收的对象,再重新建立分支引用。
注意:不要用“在目录里重新 git init”这种粗暴方式去恢复丢失的 .git 目录,除非你完全不在乎之前的提交历史。重新 init 等于新建一个仓库,旧对象虽然可能还在磁盘上,但失去了索引结构,找回成本会高很多。
5.2 “无法将 git 项识别为 cmdlet”的排查
这是 Windows 上非常典型的报错。本质是 PATH 环境变量里没有 git 的安装目录。常见场景:安装 Git 时没有勾选“把 Git 添加到 PATH”的选项;或者安装完之后没有重新打开终端。解决方法是先关闭所有 CMD、PowerShell、VSCode 窗口,重新开一个终端再敲 git --version。
如果重开还是不行,说明 PATH 确实没加。手动加环境变量的步骤是:找到 git.exe 所在目录,默认在 C:\Program Files\Git\cmd;右键“此电脑”-> 属性 -> 高级系统设置 -> 环境变量;在系统变量的 Path 中新增这个目录,确认保存后重启终端。
还有一个容易忽略的情况:在 PowerShell 里执行 git 时,如果报的是“无法将 git 项识别为 cmdlet”而不是“不是内部或外部命令”,可能是别名冲突或者执行策略问题。可以用 Get-Command git 查看当前解析到的命令,或者用 where.exe git 查看所有候选路径。VSCode 集成终端里遇到这个问题,很常见的原因是 VSCode 在环境变量加载之前就启动的,重启 VSCode 一般就好了。
5.3 冲突解决:从慌得一批到游刃有余
合分支时的冲突,是每个 Git 使用者迟早要面对的场面。出现冲突后,git status 会列出冲突文件,文件内部会插入冲突标记:
<<<<<<< HEAD 这是当前分支的内容 ======= 这是被合并分支的内容 >>>>>>> feature/login处理办法是打开文件,逐段决定保留哪边、两边都要、还是重写。手动改完后,把文件 git add 进暂存区,然后 git commit 完成合并。千万别在冲突状态下跳过 git add 直接 commit,Git 会提示有未合并路径,不会允许继续。
如果冲突文件很多,或者你想放弃这一次合并,可以用 git merge --abort 回到合并之前的状态。这是很实用的“后悔药”,建议在动手解决大冲突前先记在心里。
现代 IDE 解决冲突非常方便。VSCode 会在冲突文件处显示 “Accept Current Change”“Accept Incoming Change”“Accept Both Changes” 等按钮;IDEA 里也可以逐项选择。我的工作流是:优先用 IDE 的可视化工具,遇到特别复杂的冲突再手动编辑。另外分享几个降低冲突概率的经验:第一,功能分支的生命周期不要太长,长时间不合并,冲突概率会指数上升;第二,每次开始干活前先 pull 一次;第三,同一个团队尽量别同时改同一块代码,确实要改,提前在群里吼一声。
5.4 进阶实用:LFS 大文件和 worktree
如果你的仓库里有二进制大文件,比如设计稿、模型文件、音视频资源,普通 Git 会把文件每个版本都完整存下来,仓库体积会飞速膨胀,新成员 clone 时体验极差。这时候 Git LFS 能派上用场。初始化流程:
git lfs install git lfs track "*.psd" "*.mp4" git add .gitattributes之后这些文件的实际内容会存储在 LFS 服务器上,Git 仓库里只保留一个轻量指针。新成员克隆时,LFS 文件会按需拉取。如果遇到 git lfs clone 卡住的情况,常见原因是 LFS 文件太多或单个文件很大,可以尝试先普通 git clone,再进入目录执行 git lfs pull;或者把并发下载数量调低,例如 git config --global lfs.concurrenttransfers 1;也可以用 git lfs ls-files 查看当前管理了哪些大文件,排查是否有异常巨大的对象把拉取拖死了。
git worktree 则是另一个实用功能。它允许一个仓库同时存在多个工作目录,每个工作目录对应不同分支。比如你在开发 A 功能,突然线上有个 bug 要马上修,又不想 stash 当前工作区、带着一堆文件来回切换,就可以执行:
git worktree add ../hotfix hotfix-branch在新目录下直接进入 hotfix-branch,改完、验证、提交之后推送上去,再切回原工作目录继续手头的工作。注意同一个分支在同一时间只能在一个 worktree 中被检出版本,否则会报错。用完的 worktree 记得用 git worktree remove 清理,省得堆积一堆目录。
5.5 安全自查:别把 .git 目录暴露在线上
这个问题新手很容易忽略。如果你在部署静态站点或 Web 应用时,把整个项目目录一股脑同步到了服务器 Web 根目录,里面带着的 .git 目录就等于把完整提交历史、历史版本中的敏感配置全暴露给了能访问到的人。最简单的自查方式是在浏览器里试着访问 http://你的域名/.git/HEAD,如果返回了类似 ref: refs/heads/main 的内容,说明你的仓库元数据已经可以被外界读取。
应对方式分两层。第一层是服务器配置层,在 Nginx 或 Apache 中禁止外部访问所有以点开头的目录,比如 Nginx 可以加 location ~ /.git { deny all; }。第二层是部署习惯层,同步文件时显式排除 .git 目录,或者在部署机上重新克隆一份干净代码再发布,而不是把本地开发目录整个推上去。如果你已经发现 .git 暴露过,除了立即修复访问权限,还要尽快轮换仓库中出现过的所有敏感凭据,包括数据库密码、API 密钥、以及历史提交里可能存在的明文 token。安全问题的核心原则永远是先止血,再排查,最后预防。
在使用 Git 的整个过程中,我个人最深的体会是:大部分“危险操作”其实没那么危险,真正危险的是对仓库状态一无所知。git status 和 git log 两兄弟能解决九成疑惑,剩下的,无非就是多想一步“这次操作会不会影响别人的历史”。养成小步提交、及时同步、分支隔离的习惯之后,Git 带给你的不再是恐惧,而是踏踏实实的掌控感。希望这份笔记能帮你少踩几个我踩过的坑。