首页 / 资讯中心 / 文章详情

Git 实战指南:从初始化、分支管理到冲突解决与回滚

Git 实战指南:从初始化、分支管理到冲突解决与回滚 ★ FEATURED ARTICLE
1. 仓库初始化与基础配置1.1 从零开始先想清楚“这个仓库到底给谁用”别急着敲命令。我见过太多人一上来就git init提交了几次之后才发现用户名是全球默认值提交记录里挂着一个“unknown”或者仓库里混进了node_modules、日志文件、密钥文件这些不该提交的东西。Git 这东西最大的特点是写错的提交历史虽然能改但改起来远比第一次做对要费事得多。所以在初始化之前先花 30 秒回答三个问题这个仓库是个人项目还是多人协作代码托管平台用的是哪一家GitHub、GitLab、Gitee 还是公司内网仓库里会不会有不该进版本管理的文件回答完这几个问题下面这些命令才算是真正“对症”的操作# 初始化仓库显式指定默认分支名 git init -b main # 如果 Git 版本较老低于 2.28先改默认分支名 git init git branch -m main为什么要指定main而不是默认的master不是政治正确问题而是所有主流托管平台现在都以main作为新建仓库的默认分支。如果你本地是master推送到远端时要多写一句-u origin master别人拉下来一看分支名对不上也容易造成困惑。既然能用一条命令消除未来所有分支命名的不一致那就没必要省这一步。然后配置用户信息。这一步很多人以为是“可选项”实际上它是 Git 正常工作的前提。我在这条路上踩过坑某次在公司电脑上提交代码因为之前这台电脑的全局配置被某个老旧脚本污染了提交出来的 author 信息是一串乱码后果是 CI 系统直接把提交人判定成了机器人导致代码审查流程直接卡死后来花了大半天才把所有提交历史修复干净。# 全局配置只做一次 git config --global user.name 你的名字 git config --global user.email 你的邮箱 # 检查当前项目配置 git config --local user.name git config --local user.email # 一次性打开配置文件查看所有项 git config --list需要说明的是--global配置在个人电脑上没问题但如果是公司配发的电脑我偏向用--local覆盖为项目内专用。原因很简单多个项目的账号可能不一样全局配置一旦设错多个仓库同时受影响排查起来非常耗费时间。1.2 补齐忽略规则这一步不做后面全是坑gitignore文件的价值在初始化阶段完全看不出来但提交几次之后就明白了某天你正要提交一段新代码一git status发现满屏都是node_modules的文件路径或者.env这种带密钥的文件就静静躺在待提交列表里。这时候没人帮你兜底如果一时疏忽把密钥提交上去了那几乎等于给所有能看到这个仓库的人发了一张服务器通行证。我第一次带新人的时候遇到过类似的事故对方把数据库连接串写进了配置文件然后连着密钥一并提交到了仓库等发现时已经推送到了远端。虽然最后通过历史重写清掉了记录但这期间仓库一直处于风险暴露状态光是通知所有协作者重新克隆、处理本地残留就折腾了一整天。所以初始化的同时就要准备好.gitignore# 创建项目根目录下的 .gitignore touch .gitignore不同语言、不同框架的忽略规则差异很大我把自己常用的一个基础版本贴在下面它覆盖了最常见的几类垃圾文件# 依赖目录 node_modules/ vendor/ # 构建产物 dist/ build/ target/ *.class # 日志和临时文件 *.log *.tmp .DS_Store # IDE 配置 .idea/ .vscode/ *.iml # 环境变量与密钥 .env .env.* *.pem *.key # 测试覆盖率 coverage/这里有一个容易忽略的细节.env和.env.*之间的区别。有些项目需要保留.env.example给其他开发者做参考这个文件恰恰需要提交上去所以如果把规则写成.env*会连示例文件一起忽略掉反而给想接手项目的人添了麻烦。我一般会先写.env和.env.*再单独加一行!.env.example比如这样.env .env.* !.env.example另外提醒一句.gitignore只对“尚未被 Git 跟踪的文件”生效。如果你在补忽略规则之前已经把node_modules或者别的什么大文件git add进暂存区了那光改.gitignore是拦不住它们的得先用下面的命令把它们从索引里移出去git rm -r --cached node_modules--cached的意思是只把文件从 Git 索引里删掉但保留在本地磁盘上。这一步做完之后再执行git add .忽略规则就真正生效了。2. 日常提交与分支管理实战2.1 提交的基本循环add、commit、pull、push 的先后顺序很多人觉得git add、git commit、git push是三个机械动作背下来就行。但真正干活的时候这个顺序里有个关键的隐藏步骤push 之前先 pull——准确说是 push 之前先“同步”哪怕你觉得自己在这个项目上是唯一一个开发者。为什么因为即使是你一个人维护的仓库你也不可能保证自己在另一台电脑上没有提交过代码。更常见的情况是你前天上半夜在家里电脑上改了一版推了上去第二天到公司忘了这件事公司电脑上还留着旧代码你改完直接git push远端直接报错Non-fast-forward因为 Git 看到你的本地历史里缺少了家里那台电脑上的那次提交。解决这个问题的最佳路径是# 先看一眼工作区状态 git status # 把改动放进暂存区按模块分开提交尽量不要一个 add 全带走 git add src/components/button.js git add src/utils/format.js # 写一个能说明问题的提交信息 git commit -m 重构按钮组件的样式逻辑新增点击态 # 拉取远端改动用 rebase 方式保持历史线性 git pull --rebase # 如果没有冲突推送即可 git push这里我需要单独解释一下git pull --rebase到底做了什么以及为什么我推荐它而不是默认的git pull。默认的git pull相当于执行git fetch加git merge它会创建一个新的 merge commit把两边的历史打成一个结。短期看问题不大但一旦大量协作者都走这个方式仓库历史会长出一堆密密麻麻的分叉和合并节点后续用git log --graph查看演进过程时会非常吃力。而--rebase做的事情是把你本地那些“还没推上去”的提交先摘下来等远端的提交全部落地之后再把你的提交一次性重放到最新位置上方。这样历史就是一条直线读起来非常干净。但 rebase 有它的禁忌永远不要 rebase 已经推送出去的提交。这句话记一辈子。自己本地还没 push 的提交随便 rebase已经 push 到远端且别人可能已经基于它开发的提交一旦 rebase 会重写哈希值别人的本地历史会跟远端对不上严重的甚至会直接把伙伴的开发环境搞成一团乱麻。那有人在群里问我“不能 rebase又不想 merge 出一堆分叉怎么办”我的回答是如果这是一次早已预期到的多人协作让每个人在完成一个功能后主动 rebase 到最新main体验会好很多。相反如果你们团队习惯用 merge 保留功能分支的原貌那从一开始就要统一不要让几个人用 rebase另外几个人用 merge那才会真正制造混乱。2.2 分支管理什么时候开分支开完了怎么合回来分支不是越多越好。我见过有些新人特别喜欢每写一个小功能就开一个feature/xxx分支结果是分支列表里躺着几十个分支经常连自己都分不清哪个合并过了、哪个已经废了。不会有统一答案适用于所有团队但我自己遵循这么几条规则任何持续超过一天的开发工作绝不直接在main上做。分支命名遵循type/description格式例如feature/user-login、fix/edge-case-null、chore/upgrade-validator。这样别人一眼就能从名字里知道这个分支在干嘛。合并策略看场景简单的功能、时间短的改动用git merge --squash把所有提交压缩成一个历史非常精简。一个需要长期维护的大功能、需要保留完整演进脉络的老老实实git merge --no-ff保留一个明确的合并节点。摘一段我平时在功能分支快要收尾时的操作序列# 切到功能分支 git checkout feature/user-login # 确保功能分支已包含 main 上的最新改动避免合并时一次性冒出太多冲突 git fetch origin git rebase origin/main # 切回主干 git checkout main # --no-ff 表示强制生成合并提交方便以后回滚整个功能 git merge --no-ff feature/user-login # 推送 git push origin main这里有两点值得解释。git merge --no-ff的作用是生成一个明确的 merge commit即使这个分支的所有提交可以按快进方式直接移动它也坚持留一个合并节点。好处是万一这个功能要整体回滚只需要git revert这一个 merge commit 就能把整个功能撤掉而不是一个个找散落的小提交坏处是历史中会多一个“空”节点。我不会说--no-ff永远优于 fast-forward但我个人在做大型功能时更偏好--no-ff因为它让“功能边界”在历史中清晰可见。还有一个操作叫git branch -d feature/user-login和git branch -D feature/user-login的区别。合并完后想清理本地分支用-d检查如果某个分支已经不要了而且没合并过用-D强制删。我见过有人用-D误删了还没合并到主干的整条分支导致几天的工作直接蒸发所以每次执行删除类命令前务必要三思——如果你的工作成果只存在于这个分支删了就真没了。顺带一提git stash在这种情况下也能救急。如果你手上改到一半的代码还没写好 commit突然需要切去修一个线上 bug可以这样做git stash push -m 按钮组件样式未完成 git checkout -b hotfix/urgent-bug # ... 修复并提交 ... git checkout feature/user-login git stash popstash的本质是把当前工作区和暂存区的改动保存到一个独立的栈里然后把工作区恢复干净。stash pop会把栈顶的改动重新应用回来。需要注意的点是如果 pop 时工作区已经有改动并且两处改的是同一个文件的同一行Git 会直接拒绝并提示冲突。此时你也别慌找到冲突文件手动解决后git add即可。2.3 push 与 fetch远端同步的两个层次有些 Git 新手会把git pull和git fetch画上等号这是不对的。简单说git fetch只把远端的最新提交和分支状态下载到本地但不改变你的工作区内容。git pull等于fetch merge或fetch rebase取决于命令参数它会直接修改工作区。在动手干大活之前比如你要做一次大规模重构我的建议是先git fetch看一看远端发生了什么变化然后再决定是用 rebase 还是 merge 来整合而不是直接一个git pull让 Git 替你选择合并策略。# 将远端所有分支状态拉到本地 git fetch origin # 看看本地相对远端 main 超前/落后多少 git log --oneline main..origin/main git log --oneline origin/main..main这两条git log命令很有用第一条列出“远端有但本地没有”的提交第二条列出“本地有但远端没有”的提交。只要跑一次这两个命令双方分叉情况一目了然再结合git log --graph --oneline --all看分叉图心里就有底了。另外推送到远端时常用的-u参数值得解释一下。git push -u origin main中的-u全称是--set-upstream意思是把本地分支和远端分支的关联关系写进配置。这个关系记录在.git/config文件里设置一次之后以后直接git push、git pull就能自动匹配到关联的远端分支不必每次重复输入完整参数。很多人第一次 push 时不带-u也能推送成功但下次再 push 时 Git 会提示“当前分支没有上游分支”还得手动搭一次桥白白浪费时间。3. 冲突解决全流程演示3.1 冲突到底是怎么产生的从代码行的维度看本质先说结论Git 的合并逻辑是“基于行”的。它比对两个版本中每一行的变更如果两边改的是完全不同的文件那很好办各自共存就行如果改的是同一个文件的不同区域Git 也会自动合并因为它们互不影响。只有当两个分支里的提交修改了同一个文件的同一处位置且两边对这个位置给出了不同的新旧内容时Git 才无法自行判定于是抛出冲突。举个例子一张config.js文件里有一行export const timeout 3000;A 在上面改成了export const timeout 5000;B 在同一行改成了export const timeout 8000;当 A 先被合并进主干随后轮到 B 的合并时Git 就懵了newer 版本中的这一行原来是3000A 把它改成了5000B 又把它改成了8000那到底按哪个来这种二义性 Git 无法判断只能把决定权交还给人。另一种更常见的情况是A 在main分支上删除了一段代码块B 在功能分支上恰好也要修改这段代码块。删改冲突同样会被视为同一处位置的差异。此时 Git 给出的不是代码行级别的合并结果而是把这个文件标记成“both modified”需要你手动决定保留哪一方、改动哪一方、还是两者都删。3.2 完整走一遍制造冲突、发现冲突、解决冲突我干脆用一个演示性的场景来复现整个过程。假设这是一个网页项目大家一起维护src/utils.js初始状态是这样的export function formatDate(date) { return date.toISOString().slice(0, 10); }我和另外一位同事分别基于不同的分支改了同一个函数。同事的分支feature/date-format-pretty把它改成了带中文格式的版本export function formatDate(date) { const y date.getFullYear(); const m String(date.getMonth() 1).padStart(2, 0); const d String(date.getDate()).padStart(2, 0); return ${y}年${m}月${d}日; }我的分支feature/date-format-us把它改成了带英文形式的版本export function formatDate(date) { const options { year: numeric, month: short, day: numeric }; return date.toLocaleDateString(en-US, options); }两个分支基于同一个初始 commit 分道扬镳现在我要把同事的分支合并进我的分支或者反过来。我选择把同事的分支合并到我当前所在分支git checkout feature/date-format-us git merge feature/date-format-prettyGit 立刻提示Auto-merging src/utils.js CONFLICT (content): Merge conflict in src/utils.js Automatic merge failed; fix conflicts and then commit the result.打开src/utils.js文件会看到这样的冲突标记 HEAD export function formatDate(date) { const options { year: numeric, month: short, day: numeric }; return date.toLocaleDateString(en-US, options); } export function formatDate(date) { const y date.getFullYear(); const m String(date.getMonth() 1).padStart(2, 0); const d String(date.getDate()).padStart(2, 0); return ${y}年${m}月${d}日; } feature/date-format-pretty这三段标记的含义是 HEAD到之间的部分是当前分支我自己的分支上的内容到 feature/date-format-pretty之间的部分是对方分支上的内容。别理解反了这是新人最容易搞反的地方。接下来我需要决定这个文件最终长什么样。假设这个项目本来就是面向中文用户的所以我决定采用同事的中文格式版本但保留我这边对toLocaleDateString的考虑最终手改成这样export function formatDate(date) { const y date.getFullYear(); const m String(date.getMonth() 1).padStart(2, 0); const d String(date.getDate()).padStart(2, 0); return ${y}年${m}月${d}日; }删除所有冲突标记保存后执行git add src/utils.js git commit -m 合并同事的中文日期格式实现这样一次完整的冲突解决流程就结束了。核心口诀是冲突标记删干净双方内容按需保留最终文件确定后 add 再 commit。3.3 高效解决冲突的三条经验法则冲突这个东西解决多了就有经验我总结三条规律第一条冲突越晚爆发越难搞。如果你知道自己要做一个持续好几天的功能尽早把主干的最新改动合进来。比如每天上班第一件事就是git fetch origin、git rebase origin/main。这样每次面对的冲突范围都小而且因为上下文都在脑子里解决起来很快。如果拖到最后才一次性合并动辄几百行的冲突标记会堆成一座小山到那时候谁也不敢说自己有把握在两小时内不踩坑地解决掉。第二条能用工具辅助就不用手搓。推荐普通开发者至少学会一个可视化合并工具比如 VS Code 自带的合并编辑器GitLens 也强烈推荐、SourceTree、Fork 等。但要注意工具只是辅助呈现真正做决定还是人。从实际操作来看我在 VS Code 的冲突编辑器里看到HEAD和feature/xxx两边内容并排显示选择“Accept Current Change”“Accept Incoming Change”“Accept Both”都是即时生效的比手改冲突标记直观得多。但如果你在终端环境比如服务器上操作没有 GUI 可用那手改标记就是基本功必须熟练。第三条遇到二进制文件冲突图片、压缩包、Excel时别想太多。Git 根本没法自动合并二进制文件它只有一个选择用当前分支版本或对方分支版本。你需要在冲突文件处确认最后一版应该是哪个版本然后直接选择覆盖。这类冲突没有手写合并的可能。一条最不爱听但最真实的经验是如果某个文件频繁出现冲突而且每次都是同一个区域那大概率是你们的模块拆分不够干净。两个人长期改同一个函数、同一个配置文件说明零件的边界不清晰这不是 Git 的问题这是架构的问题。用 Git 触发的痛来反推模块设计比单纯怪自己手速不够快要有价值得多。4. 回滚与撤销的完整方案4.1 三条后悔药checkout、reset、revert别用错“回滚”这个词在 Git 语境下其实是一个很宽泛的需求集合。不同阶段、不同状态下该用的命令完全不同。我把它们分成三类场景来说明这样会更容易对上号第一类文件还没 add 到暂存区工作区脏了或者想丢弃某个文件的改动。# 丢弃单个文件的全部本地改动 git checkout -- src/utils.js # 丢弃所有本地改动危险操作慎用 git restore .这条命令的本质是用暂存区里的版本或者说最近一次 commit 里的版本覆盖工作区的文件。一旦执行你在这个文件上做的所有未提交修改都会永久消失没有后悔药可吃。第二类文件已经 add 进了暂存区但还没 commit想撤回git add操作。# 把文件从暂存区撤回工作区保持现状 git restore --staged src/utils.js或者老一点的语法git reset HEAD src/utils.js这个操作的便利之处在于它可以保留你对文件的修改只是让这些修改不再处于 staged 状态。很多人在git add之后突然发现误加了一个不该加的文件用这个命令就能把文件退回“尚未 add”的状态安全且不损失任何内容。第三类已经 commit 了想撤销这次提交。这里有两个方向完全不同的命令# 方式一git reset 会移动 HEAD并把提交历史抹掉重来 git reset --hard HEAD~1 # 丢弃最近一次提交以及所有改动危险 git reset --soft HEAD~1 # 丢弃提交但保留改动到暂存区安全 # 方式二git revert 会创建一个新的反向提交历史永不重写 git revert HEAD这两者之间的区别值得展开说说因为搞懂它们才能正确选型。git reset --hard是把 HEAD当前分支指针往回移同时把工作区和暂存区里的内容也一切都恢复到那个历史时间点的状态。但危险点在于这条命令会彻底丢弃目标提交及之后的所有本地改动。如果那部分改动还没推送给任何人那还好你在本地完成了一次“时间旅行”如果已经推送出去了不要在公共分支上执行 reset因为别人本地还保存着旧的历史你一 reset光协调大家的同步问题就能把人逼疯。git revert的逻辑则完全相反它不移动历史而是在当前历史的基础上追加一个新的提交这个新提交的“内容”恰好是撤销目标提交的改动。例如某次提交把第 10 行从 A 改成了 B那么revert生成的新提交会把第 10 行从 B 改回 A。git revert的好处是历史完整保留不存在“将来丢失提交”的风险非常适合用在已经推送出去的公共分支上。4.2 reset 的三种模式hard、soft、mixed 到底差在哪git reset除了--hard之外还有--soft和默认的--mixed不写参数时默认就是它。它们的差别主要体现在三个地方怎么处理HEAD 的位置、暂存区index的内容、工作区的内容。模式HEAD暂存区工作区典型使用场景--soft回退不变不变把最近的多个提交合并成一个--mixed默认回退回退不变把误提交的文件从暂存区撤回保留改动--hard回退回退回退完全丢弃改动彻底回到某个历史状态举例来说我连续提交了三笔改动功能做到一半觉得这三笔提交粒度太碎了想压缩成一个。可以这样做git reset --soft HEAD~3 git commit -m 合并完成用户登录功能的三个小提交注意HEAD~3表示“当前 HEAD 之前的第三个提交”这个位置。执行--soft回退之后HEAD 指针移动了但暂存区里还保留着最近三次提交的所有改动我现在直接 commit 一次就把三个碎提交压成了一个整体对历史读者相当友好。如果我想撤回“某一次已经执行了git add但还没有 commit”的修改git reset # 等价于 git reset --mixed HEAD这条命令把所有已 staged 的内容撤回为 unstaged工作区文件保持原样非常安全。4.3 回滚到某个历史提交的三种具体玩法假设你需要把整个项目的状态回滚到某个更早的提交。第一种临时看一下旧版本长什么样不改变当前状态git log --oneline # 找到目标提交的哈希比如 abc1234 git checkout abc1234此时你会进入 detached HEAD 状态游离的 HEAD。别慌它只是意味着你现在不在任何分支上而是在一个历史快照上可以随意查看、测试、甚至在这个状态下改代码。如果只想看看看完后git checkout main回到主干即可。但如果在这个状态下做了一些实验性改动想把它保留下来别忘了先新建一个分支指向当前状态git checkout -b experimental/old-version第二种把某个文件或多个文件恢复成旧版本的样子git checkout abc1234 -- src/utils.js这个操作会用abc1234提交中该文件的内容覆盖当前工作区和暂存区。注意它会同时影响工作区和暂存区如果你只想覆盖工作区执行完之后还需要用git restore --staged src/utils.js把暂存区退回去。第三种整个分支硬回滚到旧版本仅限未推送的本地分支git reset --hard abc1234执行之后分支点和工作区都回到abc1234的时刻。这个命令尽量只在确认要丢弃的改动尚未推送到远端时使用一旦推送出去了就改用git revert或者在团队同步走偏的时候再考虑 reset。4.4 一个真实的回滚事故复盘最后分享一个我曾经经历的、让人印象极其深刻的事故复盘。当时某团队在一个项目上线前发现线上环境报了一个非常严重的 bug定位后确认是最近一次合并进去的功能导致的。当时大家的第一个反应就是赶紧回滚。有成员的角度很快“直接在 master 上git reset --hard HEAD~1把那笔合并退掉不就完了”但这个 master 分支是整个团队的公共分支已经有其他成员基于它建立了好几个特性分支并且部分改动已经推送到远端。如果直接硬 reset所有协作者都会进入“历史不一致”的混乱状态。大家冷静下来之后走的是这样一套流程先git log --graph --oneline -10看清楚最近提交和合并节点的位置。确认要撤销的正是某个特定的 merge commit于是执行git revert -m 1 merge commit hash注意这个-m 1是专门用来 revert 合并提交的参数。因为合并提交有两个父提交第一个父提交是合并时所在分支的 HEAD第二个是合并进来的分支的 HEAD。-m 1表示保留第一父分支即可即回到合并发生之前主干上的状态。推送 revert 产生的提交到远端大家 pull 后一切恢复正常。这个过程中没有任何历史被重写所以其他成员只需要正常同步即可没有一个字是浪费的。从这个复盘里能学到的经验是回滚永远优先考虑 revert直接 reset 永远是最后的手段而且只能用于未推送的历史。这条规则再强调一次都不为过。5. 常见问题与排查技巧速查5.1 快速自诊法先跑这三条命令再决定要不要发帖求救遇到 Git 行为异常我的第一反应不是翻文档而是按顺序跑三条命令git status git log --graph --oneline --all -10 git branch -vvgit status告诉你仓库当前处于什么状态是否有未提交的改动、当前在哪个分支、以及是否有正在进行中的 merge/rebase/cherry-pick 操作。很多“奇怪行为”其实都是某个操作没做完、仓库卡在半中间导致的。比如你执行了一次cherry-pick遇到冲突就暂时搁置去干别的了过了半小时再回来发现git status一直提示“You have unmerged paths”。解决办法需要结合具体场景再定但如果你不先跑git status可能连问题在哪都不知道。git log --graph --oneline --all -10是一条只读命令可以以图形化的方式快速估算出最近十笔提交之间的关系图。如果你怀疑自己“回滚错了”或想确认本地和远端的差异看这张图往往一目了然。git branch -vv用于查看所有本地分支和其关联的远端分支状态领先/落后多少个提交。很多“push 不上去”“pull 报错”的问题罪魁祸首都是分支没设上游或者设了但不是你想的那一条。看这个命令的输出能很快锁定问题范围。5.2 高概率踩坑清单每个我都亲眼见过或亲手踩过现象根本原因快速处理办法push 被拒绝提示non-fast-forward本地落后于远端git pull --rebase整合后再 pushgit status里文件删了但不知道谁删的检查工作区文件是否被手动删除git checkout -- 文件名恢复或git rm正式记录删除在错误分支上改了一堆代码没检查当前分支就开始干活千万别直接 commit可以用git stash暂存切到正确分支后git stash pop误把大文件提交进仓库仓库体积膨胀.gitignore 漏配或临时文件被推上去了第一优先级用git rm --cached移出索引同时把文件从历史里抹掉需要额外重写历史推送到公共分支前处理代价极大合并或 rebase 进行到一半卡住了想放弃当时选择保留现场还是终止操作终止合并git merge --abort终止 rebasegit rebase --abort注意如果能解决就尽量解决abort 会失去所有冲突解决的进度commit message 写错了commit 已经生成了但内容没错如果没推送git commit --amend直接修如果已推送谨慎决定是否需要 force push搞不清楚 fetch 和 pull 用一个就得了没有理解远端跟踪分支的概念多用git fetch看差异再手动整合效果比盲目 pull 可控得多这个表里的每一项都来自实操中的事故案例。有的看起来很基础但恰恰是这些“基础场景”在高压环境下最容易出错。比如第 3 条“在错误分支上改了一堆代码”几乎每个开发者都干过当时最大的心理压力是“我这几小时的工作是不是白费了”。正确答案是完全不至于。用git stash暂存切到正确分支再stash pop改动几乎原样保留顶多重新处理一下冲突标记。这种事经一次教训第二次处理就会快很多。5.3 关于 force push 的一个清醒备忘git push --force或者更精确的git push --force-with-lease是 Git 世界里最有争议的操作之一。--force的意义简单粗暴用本地的历史数据强制覆盖远端的同名分支。如果你在公共分支上乱用别人已经推送上去的提交就会从远端消失而且消失得悄无声息。更可怕的是因为《Git 分布式设计》的特性别人本地不一定已经 fetch 到你的新历史他们会浑然不知地继续在自己旧历史的基础上 commit最终导致一串串更大的混乱。--force-with-lease稍微聪明一点它允许你强制推送但前提是远端分支的状态和你上一次从远端抓取到的状态一致。换句话说如果有别人又往远端提交了新东西--force-with-lease会拒绝你的强推从而在一定程度上防止了误操作覆盖。我的建议是公共分支上用--force-with-lease是底线操作--force尽量只在“个人专用分支”上使用。所谓的“个人专用分支”不仅是名字上标明了feature/xxx更关键的是确认没有其他人拉取过这个分支的提交。5.4 一些能给工作提效的小技巧最后分享三个我日常工作里用到的、能显著减少重复劳动的小技巧第一个利用git reflog找回一切。git reflog基本可以理解为“Git 的后悔日志”它记录了每一次 HEAD 变动。如果有一天你误执行了git reset --hard想去恢复原来的状态但有忘记原来那个 commit 的哈希别慌git reflog会输出最近一段时间内 HEAD 移动过的所有位置在里面找到目标提交的哈希然后用git cherry-pick或者git reset回去即可。可以说它就是 Git 用户在走投无路时最后的救命稻草。git reflog第二个写好一个好的 commit message 模板。别小看这个团队协作时意义极大。我个人常用的模板type: subject BLANK LINE - 变更了什么 - 为什么变更 - 可能影响什么type 用feat新功能、fix修复、refactor重构、chore杂项等。使用这个模板后翻看git log --oneline时几乎能做到“一目了然”并且 git blame 时也不会一头雾水。第三个小心使用git add .多用手动 add 指定文件。git add .虽然方便但它会把当前目录下的所有未跟踪文件和修改过的文件全部装进暂存区。在一个充满临时文件、调试输出或自动生成文件的仓库里这几乎必然会引入垃圾提交。为了降低风险我的习惯是先git status看一遍再git add file1 file2精确添加最后再git status确认一下再 commit。慢是慢一点但几乎不会出错。等熟练之后你甚至会习惯把git add和git commit合并成一条命令的组合来用比如git add src/utils.js src/constants.js git commit -m fix: 修复日期格式化函数对空值的处理这一串写法的好处是如果git add因为路径不存在或有问题而失败后面的会让 commit 自动不执行配合良好的习惯几乎不会产生“提交了一堆多余文件”的尴尬。结尾的一点经验之谈Git 这东西说到底是分布式版本管理的工具它不限制你怎么用但坑不坑人完全取决于使用习惯。我这些年最大的体会是不要在压力最大的时刻去尝试不熟悉的高级命令。真正能把 Git 用得稳的人其实靠的不是花样百出的命令而是扎实掌握那几条常用命令的正常工作方式然后在遇到无法理解的意外时先跑git status和git reflog看清楚自己在哪里再决定下一步怎么走。磁盘上的代码没了可以重写历史乱了也能翻 reflog 找回来但团队协作中的信任一旦被误操作消耗掉修复成本远高于任何一条命令能带来的便利。希望这篇分享能帮你在日常开发里少踩几个坑也让团队里对 Git 不太熟的新人能少一趟浑水。
阅读完成 · 觉得有帮助?
咨询建站