Git 实战笔记:从核心原理到疑难杂症排查
做开发这些年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 跨平台安装 GitWindows / 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避免自动转换行尾带来的无意义 diffEnable 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_emailexample.com这里提醒一下user.email 建议和远程仓库Gitee、GitHub、GitLab 等注册邮箱保持一致这样提交记录才能和账号关联上否则首页上的贡献图可能看不到你的记录。也可以给某个仓库单独设置身份在仓库目录下执行 git config user.name xxx去掉 --global 即可。配置生效的优先级是系统级大于全局大于仓库级仓库级配置会覆盖全局配置。接下来是 SSH 密钥。很多人用 Gitee 或 GitHub 时遇到“认证失败”多半是密钥没配好。生成密钥的命令如下ssh-keygen -t ed25519 -C your_emailexample.com一路回车即可生成的默认位置是 ~/.ssh/id_ed25519 和 id_ed25519.pub。公钥内容可以安全公开拿它去配到平台的“SSH 公钥”设置里私钥绝对不能泄露也别提交到仓库。Windows 上公钥路径一般在 C:\Users\你的用户名.ssh\id_ed25519.pub用文本编辑器打开复制即可macOS / Linux 可以用 cat ~/.ssh/id_ed25519.pub 查看。配好公钥后用以下命令验证是否连通ssh -T gitgitee.com第一次连接会提示是否信任主机指纹输入 yes 保存到 known_hosts 就行。如果显示你的用户名并提示认证成功说明密钥已经生效。很多新手以为生成密钥就完事了其实后面还有一层 ssh-agent 用来缓存私钥。如果你有多个密钥文件或者放在非默认路径就可能需要在 ~/.ssh/config 里指定具体用哪一把私钥否则 SSH 可能拿着错误的密钥去连接反复报 Permission denied。2.3 SSH 认证失败的常规排查SSH 认证失败是我见过的高频问题报错通常是 Permission denied (publickey)或者类似 gitgitee.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 参数可以直接指定。如果你用的版本比较老也可以先把默认分支名配置成 maingit config --global init.defaultBranch main。这一步不影响功能但能让新仓库的分支命名保持一致省得以后团队里有人用 master 有人用 main看着都头疼。初始化之后马上创建 .gitignore 文件把 node_modules、target、dist、.idea、*.log 这些不该提交的内容写进去。这个文件越早建越好别等到误提交一堆依赖包之后再费劲清理。我会把常用的 .gitignore 规则放在自己的模板里新项目直接复制省心很多。接着是第一次提交的经典三连git add . git commit -m init: project setupgit 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 mergegit 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 和交互式 rebasegit commit --amend 是“修改最后一次提交”的命令。常见用法有两种补充遗漏的文件或者修改提交信息。比如提交完之后发现忘了加一个文件git add forgotten.txt git commit --amend注意amend 本质上不是修改旧提交而是用一个新的提交替换掉旧的。旧提交对象其实还在对象数据库里只是没有任何引用指向它之后会被垃圾回收。因此如果你已经把这个提交推送到了远程共享分支请谨慎使用 amend别人拉取时会遇到“远端历史与本地历史分叉”的尴尬。对于还没推送的本地提交amend 完全安全可以放心用。如果连续几次提交都不满意想重新整理多个提交就需要交互式 rebasegit rebase -i HEAD~3这会打开一个编辑器列出最近三个提交。你可以把 pick 改成 reword、squash、fixup、drop 等操作。我最常用的是 squash把几个“WIP”中间态提交压缩成一个完整的功能提交。rebase 同样有一条铁律不要对已推送的公共分支进行 rebase否则等于改写别人已经拉取的历史。个人功能分支上可以放心随便 rebase推送前把历史整理干净团队 review 时观感会好很多。4. 多环境协作远程仓库与免密配置4.1 关联远程仓库与拉取流程在 Gitee 或其它平台上建好空仓库后本地关联远程git remote add origin gitgitee.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 的默认行为为 rebasegit 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 默认使用 osxkeychainLinux 上可以用 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:tokengit.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 直接 commitGit 会提示有未合并路径不会允许继续。如果冲突文件很多或者你想放弃这一次合并可以用 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 带给你的不再是恐惧而是踏踏实实的掌控感。希望这份笔记能帮你少踩几个我踩过的坑。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →