ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Git 不只是命令:理解分布式版本控制与分支工作流

Git 不只是命令:理解分布式版本控制与分支工作流 1. Git 不是背命令是把工作流想明白1.1 它解决的根本问题是什么Git 是个版本控制系统但“版本控制”这四个字听起来太抽象了。我更喜欢把它理解成给项目装了一台时间机器 一个多人在线协作的底盘。你写的每一行代码、改的每一个文件只要提交过一次之后不管怎么折腾都能回到那个时间点。这个能力对于个人写 Demo 可能觉得无所谓但一旦代码量上来了、合作的人超过两三个没有这套机制项目大概率会变成“最终版V2”“最终版V3_不要动”“真最终版_别改”这种灾难现场。在众多版本管理工具里Git 能成为事实标准核心原因在于它是分布式的。这个词天天被提起但很多人并没有真的理解它意味着什么。简单说每个人的本地目录都是一个完整仓库不是从服务器拉下来的一个“副本”而是一份拥有全部历史记录的独立仓库。所以哪怕远程服务器挂了、网络断了你手里的代码历史和分支照样完整还可以继续提交、继续开发。这一点和早期集中式的 SVN 有本质区别SVN 断了网基本没法提交Git 断网只是提交到本地等网络恢复再推上去就行。1.2 先说清楚 Git 和 SVN 到底差在哪很多人在学 Git 之前用的是 SVN或者干脆是直接拿 IDE 里那个“同步”按钮当保存用从没真正关心过后台跑的是什么。Git 和 SVN 最大的差异除了上面说的分布式之外还有一个体感非常明显的地方分支。SVN 里分支是一个目录的完整复制建分支要等半天合分支更是心惊胆战。Git 里分支只是一个指针创建分支的开销几乎是零随便建、随便合并、随便丢弃。刚接触 Git 的人最容易被这个概念绕晕分支不就是一个文件夹吗怎么就是个指针了我打个比方把你的代码仓库想象成一本笔记本每一次提交就是在笔记本上盖一个图章盖完图章之后你会写一行字记下这次图章盖在了哪一页。整个仓库的历史就是一系列图章记录。分支就是一根“当前我在第几页”的书签它本身不存任何页的内容只是指向某一页。所以你创建新分支等于拿一张新书签放进笔记本成本当然低。理解了这一点后面所有命令——checkout、merge、rebase——都会变得顺理成章。2. 安装、初始化和那堆必须提前配好的东西2.1 Windows 上怎么装最省事Windows 下现在基本都是直接下载安装包然后一路 Next。但有几个选项值得注意很多人图省事直接全默认装完之后才发现命令行里用不了。我现在常用的方式到官网下载最新的 Git for Windows安装时到“Adjusting your PATH environment”这一步选中间那个Git from the command line and also from 3rd-party software这样不仅能从 Git Bash 里用 Git还能直接在 PowerShell、CMD 里调用 git 命令。很多人在终端里输入 git 提示“无法将 git 项识别为 cmdlet、函数、脚本文件或可运行程序的名称”八成就是这一步选了第一个“仅从 Git Bash 使用”或者安装时没有把 PATH 配置加进去。安装完成后打开一个新的 PowerShell 窗口先验证一下git --version输出类似git version 2.40.0.windows.1就说明装好了。如果还是提示找不到命令先别急着卸载重装检查一下环境变量 Path 里有没有C:\Program Files\Git\cmd这个路径偶尔会有安装器把路径写到了用户变量而不是系统变量手动加一下就行。另外说个小坑千万注意 Git Bash 和系统自带 CMD 的编码问题。Windows 上默认编码是 GBK而 Git 相关操作输出的是 UTF-8。你在 Git Bash 里看中文文件名或者提交信息乱码通常就是因为核心的core.quotepath设置没改这个下面配置章节会处理。2.2 macOS 和 Linux 上的安装macOS 的话如果你装了 Homebrew一条命令就搞定而且这种方式装的 Git 通常比系统自带的版本新得多brew install gitLinux 发行版用各自的包管理器Ubuntu/Debian 系是sudo apt install gitCentOS/RHEL 系是sudo yum install git或sudo dnf install git。不管什么系统装完第一件事都是验证版本。2.3 全局配置不配好后面全是折磨安装完成之后第一步不是急着创建仓库而是设置身份信息。Git 每一次提交都会记录作者名字和邮箱而且这俩信息会永久留在历史里改起来非常麻烦。如果你后续要把代码推到 GitHub 或 Gitee 这类平台建议直接使用平台上绑定的邮箱这样提交记录才会正确关联到你的账号。git config --global user.name 你的名字 git config --global user.email 你的邮箱这里再顺手把几个高频配置一起做了# 让 Git 正确处理中文文件名不显示成八进制转义序列 git config --global core.quotepath false # 设置默认编辑器提交信息没写全时会打开这个编辑器让你补 git config --global core.editor code --wait # 设置 push 的默认行为只推送当前分支到同名的远程分支 git config --global push.default simple有一个配置我建议你搞清楚再决定要不要改autocrlf。Windows 上安装 Git 时安装向导默认会让你选Checkout Windows-style, commit Unix-style line endings翻译成人话就是检出文件时把换行符转成 Windows 的 CRLF提交时再转成 LF。这个设置在 Windows 下能避免很多莫名其妙的换行符问题但如果你和 macOS 或者 Linux 的同事协作同一个仓库反而会因为自动转换产生“整个文件都被认为是改动过的”的闹心情况。我的做法是个人项目直接统一设成 LF团队项目遵循团队约定别一个人擅自改。2.4 SSH 密钥配置这步少了推送永远是噩梦在 GitHub 上提交代码最常用的是 HTTPS 方式但每次 push 都要输账号密码体验极差。后面我会专门讲怎么缓存凭据但更好的方案其实是直接用 SSH 密钥。生成密钥本身很简单ssh-keygen -t ed25519 -C 你的邮箱一路回车即可默认生成在~/.ssh/id_ed25519.pub。然后把id_ed25519.pub里的内容复制到 GitHub 的 Settings → SSH and GPG keys或者 Gitee 的设置页里。验证是否成功ssh -T gitgithub.com能看到Hi username! Youve successfully authenticated类似的输出就通了。这里常遇到的一个报错是Permission denied (publickey)绝大多数原因是密钥没添加到 ssh-agent或者生成密钥时指定了非默认文件名。如果指定了文件名比如id_ed25519_company需要这样处理ssh-add ~/.ssh/id_ed25519_company并且要在~/.ssh/config里加一段Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_company之前我接过一个求助对方明明密钥配好、公钥也粘贴到网站上了就是报同样的错误。排查了半天发现是公司电脑上~/.ssh/config里把github.com指向了内网的一个 GitLab 地址机器以为自己要连的是内网服务器自然用错了密钥。这种问题如果你也遇到了优先检查 config 文件。3. 日常操作速查真正常用的命令不超过十个3.1 创建仓库与提交代码的完整闭环很多人在 IDE 里点按钮点了很久其实命令行核心流程非常短。我建议新手先在命令行把这一整套走顺了再去用 IDE 的图形界面——不然出了问题你根本不知道按钮背后执行了什么。从零开始一个项目git init git add . git commit -m init project三行命令一个本地仓库就建立好了。git init会在当前目录生成一个隐藏的.git文件夹整个仓库的元数据、对象、历史记录都在里面。git add .是把当前目录下所有变动过的文件包括新文件和修改过的文件标记为“暂存”然后git commit才是真正把暂存内容固化成一个不可变的快照。这里有个初学者必踩的坑有些人会问为什么我已经git add .了git status还是能看到文件是红色的这是因为git add之后如果没有git commit文件只是处于暂存区改动并没有真正进入版本历史。git status里红色表示未暂存绿色表示已暂存commit 之后才是干净状态。还有一个很容易犯的低级错误在 GitHub 网页上新建了仓库然后本地项目想直接推上去。本地明明什么都没提交一 push 就报error: src refspec master does not match any意思是你本地根本没有这个分支可以推。解决顺序是本地先至少 commit 一次再添加远程地址最后 push。3.2 分支、合并与冲突处理前面说了分支就是书签。日常开发几乎可以总结成三步从主分支拉一个功能分支改完代码提交然后把功能分支合并回主分支。# 查看当前分支带 * 号的就是当前所在分支 git branch # 创建并切换到新分支 git checkout -b feature/login # 或者 Git 2.23 推荐的方式 git switch -c feature/login这里我多说一句如果你还停留在git checkout这个老命令上建议尽快熟悉git switch和git restore。因为 checkout 命令承担了太多职责——切换分支、恢复文件、甚至更新远程分支引用一个命令干三件事反而容易让人混淆。switch只负责切换分支restore只负责恢复文件语义清晰太多了。合并分支最常见的方式# 先切回主分支 git switch master # 把功能分支合并进来 git merge feature/login如果两个分支都改了同一个文件的同一段代码Git 没法自己判断该保留哪边就会提示冲突conflict。冲突文件的标志性特征是内容里出现一堆 HEAD、、 feature/login。不要怕冲突处理思路就一句话打开文件逐段决定保留哪个版本、还是两边都留删掉分隔符保存然后git add这个文件再git commit完成合并。比较取巧的经验如果你只是想把一个分支完整合到主分支而且确定主分支没有新的提交用git merge不会遇到冲突。冲突只发生在两边都改过同一位置。所以干活之前养成一个习惯——切功能分支之前先把主分支更新到最新这样合并的时候顺滑很多。3.3 远程仓库的日常协作代码不止有本地仓库最终要放到远程仓库GitHub、Gitee、GitLab、公司内网服务器等等。添加远程地址git remote add origin gitgithub.com:username/repo.git git push -u origin master-u的作用是把本地 master 分支和远程 master 分支建立追踪关系以后直接敲git push和git pull就行不用每次带远程名和分支名。日常拉取代码我建议记住一个原则先 pull 再 push。不要在自己本地改了一堆之后直接 push因为远程可能已经被别人更新过了。先git pull把远程最新的改动拉下来合并到本地再推上去。如果git pull报冲突别慌冲突文件处理方式和 merge 完全一样因为它们底层做的是同一件事。另外说一下 fetch 和 pull 的区别很多人背了命令但没懂原理。git fetch只是把远程的提交记录下载到本地并不会改动你当前工作目录。git pull实际上是fetch merge两个动作。如果你不想合并只想看看远程有什么新东西用 fetch 更安全。还有一个高频动作看提交历史。最简单的git log --oneline --graph --all能以图形化方式展示所有分支的提交路径。我会在调试时用git log -p看某次提交的具体改动或者git blame定位某一行代码是谁改的、为什么改的。这些命令平时可能用不上但出了问题排查时能救命。4. 几个操作感极强的高效技巧4.1 git commit --amend提交错了到底怎么收拾git commit --amend是网上搜热词里非常靠前的一个因为几乎每个人都会遇到“提交信息写错了”“提交完之后发现少加了一个文件”“上一个提交的代码有 bug 想改进去”之类的场景。它的作用一句话把当前暂存区的内容合并进上一次提交并且允许你重新写提交信息。# 场景一上次提交信息写错了 git commit --amend -m 正确的新提交信息 # 场景二上次漏提交了某个文件 git add 漏掉的文件 git commit --amend --no-edit--no-edit表示沿用上次的提交信息不重新打开编辑器。这个命令用起来很舒服但有一个极其重要的节制原则amend 只适合处理还没有推到远程的提交。如果上次提交已经 push 到了远程分支而你本地又 amend 了那么本地和远程的历史就产生了分叉——本地上一次提交的哈希值变了远程还是旧的那个。这时你再 push 会被拒绝只能git push --force强制覆盖而这会直接改写远程历史协作中非常危险。所以我的实操经验是本地提交随便 amend一旦 push 出去了就老老实实新增一个修复提交。远程历史已经公开强行改写容易把同事的本地分支搞得一团糟。4.2 git worktree同一仓库同时开多个分支git worktree是个非常实用但对很多人来说还比较陌生的功能。它的能力是从同一个仓库里额外 checkout 出一个独立的工作目录互不干扰。啥意思平时你切换分支必须先把当前工作区处理干净比如先 commit 或者 stash然后才能切到另一个分支。如果你在改 A 分支突然线上有问题需要紧急修复 B 分支你只能停下手里的事。用 worktree 就不用git worktree add ../repo-hotfix -b hotfix/issue-123这会在../repo-hotfix目录下创建一份完整可用的工作区并自动创建hotfix/issue-123分支。你可以在主目录继续写 A 分支的代码去repo-hotfix目录里修复线上问题两边代码完全隔离。用完之后记得清理git worktree remove ../repo-hotfix git worktree prune我没见过几个教程认真讲 worktree但其实它非常适合那些同时维护多个项目版本的场景。比如你有一种旧的稳定版本在维护又要在新版本上开发用 worktree 可以同时开着两个目录不需要反复切分支、不需要 stash、不需要担心切来切去把工作区搞乱。4.3 清除账号密码缓存凭据泄露不是小事网上搜 “git 清除账号密码” 的热度很高说明很多人因为 HTTPS 方式推送时输入过账号密码这些凭据可能已经被系统缓存了自己却不知道什么时候该清除。Windows 上 Git 的凭据管理器通常存储在 Windows 凭据管理器里。要清除git credential-manager uninstall或者去控制面板的“凭据管理器”里把 git 相关的凭据删掉。macOS 则是git credential-osxkeychain管理的钥匙串。Linux 比较常见的是~/.git-credentials文件直接删掉即可。如果你受够了这个反复输入和缓存带来的凭据问题建议直接切到 SSH 方式把远程地址从https://github.com/...改成gitgithub.com:...即可。SSH 的公私钥认证本质上就是文件级别的认证不依赖账号密码缓存安全性和便利性都更好。5. 常见问题排查与故障修复实录5.1 SSH 认证失败报错现状git push时提示Permission denied (publickey)或者gitgithub.com: Permission denied (publickey). fatal: Could not read from remote repository.排查步骤一步步来确认公钥已经添加到平台账号里。GitHub 和 Gitee 都有各自独立的 SSH key 管理入口别只在本地生成密钥就以为万事大吉。确认 SSH agent 里有你的私钥。ssh-add -l能列出当前加载的密钥如果为空或没包含你用的那个ssh-add ~/.ssh/id_ed25519手动加一次。检查~/.ssh/config是不是把域名映射到别的服务器了这是最容易忽视的坑。实在不行用ssh -vT gitgithub.com打开调试模式看到底哪一步失败了这一步能看到非常详细的连接和密钥校验过程。这类问题几乎都是配置层面的问题不是代码问题所以别慌慢慢排查就好。5.2 fatal: not a git repository这个报错的完整形式是fatal: not a git repository (or any of the parent directories): .git。意思是当前目录不是 Git 仓库往上一层找也没找到.git目录。新手碰到这个报错通常是因为忘掉了git init或者在一个子目录里执行了 git 命令但这个仓库的根目录并不在这里。解决要点就一句话你的 git 操作命令必须在仓库根目录下执行或者至少是在仓库目录的子目录下执行因为 Git 会向上级目录递归寻找.git文件夹。如果你确定自己就在仓库目录里但还是报这个错检查一下是不是当前目录被误当成了 Git 仓库。这种情况往往是因为你从一个归档包里解压了代码但归档包里没有包含.git文件夹这是正常的.git一般不会被打进发布包导致代码文件在版本历史不在。5.3 无法将“git”项识别为 cmdlet、函数、脚本文件或可运行程序的名称这句话在 Windows PowerShell 下非常常见。本质是系统的 PATH 环境变量里没有包含 git 可执行文件的目录。处理方式有两种# 临时在当前窗口生效 $env:Path ;C:\Program Files\Git\cmd # 永久生效系统设置 → 环境变量 → 用户变量 → Path → 新建 # 添加 C:\Program Files\Git\cmd这里注意Git 安装装完整的情况下不是把 PATH 指向Git/bin而是Git/cmd后者才是官方建议加入 PATH 的目录。当然很多新版安装器已经自动配置好了如果你用的是绿色版、免安装版或者通过某些工具包间接安装的才需要手动配置 PATH。还有一个非常诡异的场景你安装了多个版本的 Git或者系统里有另一个叫做 git 的可执行文件干扰了。我在一台机器上遇到过类似的报错最后用where.exe git查了一下发现它指向了一个并不存在的路径——之前的 Git 被卸载了但 PATH 里的残留还留着。所以遇到这个问题不仅要去加路径还要检查有没有残留的失效路径有就顺手清掉。5.4 分支合并冲突解决的正确打开方式冲突是 Git 里最让新手害怕的东西但它其实是正常现象。Git 是一个工具它不是魔法当两个分支各自修改了同一个文件的同一段它没办法替你判断哪个才是“正确”的版本只好把双方都摆出来让你自己选。冲突出现时的报错大概长这样CONFLICT (content): Merge conflict in src/main.js Automatic merge failed; fix conflicts and then commit the result.打开冲突文件你会看到 HEAD 这是当前分支的版本 这是要合并过来的分支的版本 feature/login处理思路很简单审查每一段冲突内容想清楚保留哪一边。如果你想保留当前分支的版本删掉 HEAD、、 feature/login这三行标记以及下面那段“要合并过来的版本”。如果两边都要就把三行标记删除保留两边内容并手工调整为最终样子。保存文件执行git add 冲突文件名再git commit。这里的坑主要是有人会忘记删除标记行直接保存了这会让代码处于语法错误的状态。还有一些 IDE 有“接受当前版本”“接受传入版本”这类按钮用起来快但要理解它背后的逻辑别只是无脑点。关于冲突我想特别提一个经验很多冲突其实是换行符差异造成的。Windows 和 macOS 开发者混合作时如果一个文件被两边分别用 CRLF 和 LF 处理过Git 会把整份文件都标为“有冲突”。处理这个问题的方法不是反复解决而是统一在仓库根目录下加一个.gitattributes文件声明所有文件的换行符行为* textauto *.sh text eollf *.bat text eolcrlf有了这个文件Git 在比对时就会按统一规则处理换行符不再把“换行符不同”和“内容不同”混在一起。5.5 常见问题速查表现象原因解决方式push 被拒绝提示 non-fast-forward远程有本地没有的新提交先 git pull解决可能存在的冲突后再 push命令报错 not a git repository不在仓库目录内或从未 git init切到仓库根目录或重新 git init中文字段或文件名显示成 \xxx 形式core.quotepath 未设置git config --global core.quotepath falsepull 时提示冲突本地与远程改了同一位置处理冲突文件git add 再 commit提示 You are in detached HEAD state检出了某个历史提交而非分支git switch 到目标分支即可误删了分支无法恢复分支被删除如果删除前记录了哈希值git branch 分支名 哈希值 可恢复6. 最后分享几个我用 Git 的微习惯给别人讲 Git最终能落地的都是习惯。有几个动作我几乎每次操作都会用到这里一并分享出来。第一commit 要小而准。不要攒了一周的工作量一次性git add .然后提交一个“更新代码”。别人看历史的时候根本不知道你改了什么回滚也无从下手。我一般遵循“一个逻辑改动一次提交”的原则哪怕只是改了 3 行代码逻辑完整就可以提交。提交信息用简洁的陈述句比如“修复登录页在移动端溢出”别写“修改”这种提交信息等于没写。第二push 前必看git status。很多人 push 完才发现自己把不该提交的配置文件推上去了比如.env里带着数据库密码。我的习惯是每次提交前先git status看一眼改动文件列表再git diff看一眼关键改动内容确认没有敏感信息、没有无关文件才 add 和 commit。一旦敏感信息被推送上去哪怕只存在一分钟都得走上“清理历史记录”这条路非常被动。第三善用.gitignore。很多新人一开始不知道这东西以至于把node_modules、target、bin、obj这些依赖目录和构建产物都提交进了仓库导致仓库体积快速膨胀clone 都要等半天。正确做法是项目一开始就写好.gitignore。GitHub 官方有一个 gitignore 模板仓库可以直接按语言拉取对应模板再用比如 Java 的忽略target/Node 的忽略node_modules/Python 的忽略__pycache__/和.venv/。第四不要盲目用git push --force。它确实能解决本地与远程分叉的问题但也意味着你告诉 Git “别管两边有没有分叉直接用我本地的历史覆盖远程”。协作中一旦覆盖了别人的提交那部分代码几乎没有概率找回来。现在很多远程仓库也默认拒绝非 fast-forward 的强制推送就是出于这个保护目的。如果确实需要使用强制推送来纠正错误优先考虑git push --force-with-lease它会先检查远程分支是不是自己最后一次看到的状态如果是才允许强制覆盖。这比裸的--force安全得多。第五不要忘记git stash这个临时工。经常会有这种场景你正在 A 分支改代码领导突然让你先处理一个紧急 bug但手头代码还没改完不能提交、又不想丢。git stash可以把当前所有未提交的改动临时存起来让你干净地切走。处理完紧急任务之后切回来用git stash pop恢复之前的工作。stash 的暂存区是独立于 commit 历史的所以不会污染仓库记录。我也见过有人拿 stash 当作“临时加密箱”用开多个 stash然后自己也分不清哪个是哪个了。所以 stash 的命名尽量带上说明比如git stash push -m 登录页样式修改中。第六遇到不会的报错先看git help。Git 内置的帮助信息其实写得非常详细。git help 命令会直接打开完整的 Unix 风格手册页包括命令的所有可选参数、使用示例和注意事项。看这个比自己到处乱搜省时间得多。最后说一句我的体会。Git 命令其实没有想象中那么多日常八成就用那十几个命令反复用熟了之后真正难的不是命令本身而是脑子里有没有一张清晰的“项目状态图”——你当前在哪个分支、哪些文件改过了、哪些还没暂存、本地和远程差了多少提交。只要这张图是清晰的Git 对你来说就是一个无比顺手的工具而不是一门必须背诵的科目。
返回列表