checkout 和 reset 到底改了哪里:HEAD、分支、index、worktree 的变化
checkout 和 reset 到底改了哪里HEAD、分支、index、worktree 的变化前面已经讲过 Git 里最容易混淆的三份状态HEAD当前提交或者当前分支指向的提交。index下一次提交准备包含什么。worktree你磁盘上真正看到、正在改的文件。再加上一个分支引用refs/heads/master、refs/heads/feature分支名本质是指向 commit 的指针。checkout和reset难理解是因为它们经常同时影响这几样东西。如果只记一句话checkout 更像“换位置”reset 更像“挪当前分支”。但这句话还不够。真正要弄清楚要问三个问题HEAD最后指向哪里index会不会变worktree会不会被覆盖checkout branch换到另一个分支入口假设当前状态是master - commitAfeature - commitBHEAD - master执行mgit checkout feature结果是HEAD - refs/heads/featureindex - commitB 的 treeworktree - commitB 的 tree也就是说checkout feature做了两件事。第一切换当前所在分支HEAD 从 master 换到 feature第二把暂存区和工作区恢复成 feature 指向的提交快照index/worktree 变成 commitB 的样子所以你执行完 checkout 后会看到文件内容也跟着变了。这就是很多人第一次用 Git 时困惑的地方我只是切换分支为什么文件也变了因为分支不是一个目录副本。分支只是一个指针。checkout 做的是“让当前工作区呈现这个指针指向的快照”。checkout commit进入 detached HEAD如果不是 checkout 分支而是 checkout 一个 commitmgit checkout HASH这时HEAD不再指向某个分支而是直接指向某个提交HEAD - HASH这叫 detached HEAD。人话就是你站在了某个历史提交上但没有站在任何分支入口上。在 detached HEAD 状态下继续提交新的 commit 不是自动挂在 master 或 feature 下面的。真实 Git 会提示你小心这一点。这不是 Git 把历史弄坏了而是你暂时离开了“分支名”这个入口。checkout 为什么要检查工作区checkout 会改worktree。只要一个命令会改工作区就必须考虑一个风险会不会覆盖用户还没提交的修改比如你当前有a.txt 在 worktree 里被手动改了但还没有 add也没有 commit这时 checkout 到另一个分支如果目标分支里的a.txt内容不同就可能直接覆盖你的修改。mini-git 的策略比较保守如果 index 和当前 HEAD 不一致说明有 staged changes拒绝 checkout。如果 worktree 文件内容和 index 不一致说明有 unstaged changes拒绝 checkout。如果目标 tree 会覆盖未跟踪文件也拒绝 checkout。这比真实 Git 的某些情况更严格但更适合学习。因为它把规则讲清楚了会写 worktree 的命令不能随便覆盖用户本地修改。源码里cmd_checkout.c的主线也是这个思路解析目标 branch/commit检查当前 index/worktree 是否安全读取目标 commit 的 tree恢复 index 和 worktree更新 HEAD其中安全检查就是 checkout 命令最重要的工程细节。reset移动当前分支reset的核心动作不是“切换分支”而是把当前分支指针移动到另一个 commit。假设现在是HEAD - master - commit2commit2 - parent commit1执行mgit reset --mixed commit1结果不是 HEAD 换到另一个分支而是 master 本身被挪了HEAD - master - commit1这就是 reset 和 checkout 的一个关键区别checkout branchHEAD 换到另一个分支入口。reset commit当前分支入口本身被移动。所以 reset 的危险性更高一点。它会改变当前分支指向的提交。不过注意reset 不会删除 commit 对象。commit2 只是暂时没有分支指向了对象还在.git/objects里。真实 Git 可以通过 reflog 找回mini-git 里也会记录 reset 的 reflog。reset 的三种模式reset 难点在于它有三种模式。模式移动当前分支更新 index更新 worktree--soft是否否--mixed是是否--hard是是是一句话版soft只撤提交不动暂存区和工作区。mixed撤提交也撤 add但保留文件修改。hard撤提交、撤 add、覆盖工作区。真实 Git 里reset默认是--mixed。mini-git 也保持了这个默认行为。用两次提交理解 reset假设你做了两次提交commit1a.txt onecommit2a.txt two现在状态是HEAD - master - commit2reset --soft commit1执行mgit reset --soft commit1结果master - commit1index 仍然像 commit2worktree 仍然像 commit2适用场景提交写错了想撤掉 commit但保留“已经 add 好”的状态重新 commit。reset --mixed commit1执行mgit reset --mixed commit1结果master - commit1index - commit1worktree 仍然像 commit2适用场景提交不想要了add 状态也不想要了但文件修改还想保留。这也是最常用的撤提交方式。reset --hard commit1执行mgit reset --hard commit1结果master - commit1index - commit1worktree - commit1适用场景当前修改完全不要了工作区也要回到 commit1。这也是最危险的模式因为它会覆盖工作区。所以reset --hard之前要先确认当前未提交修改真的不需要了。checkout 和 reset 的核心区别可以这样对比命令更像什么主要影响checkout branch换到另一个分支入口HEAD、index、worktreecheckout commit站到某个历史提交HEAD、index、worktreereset --soft挪当前分支branch/HEADreset --mixed挪当前分支并重置暂存区branch/HEAD、indexreset --hard挪当前分支并覆盖到目标快照branch/HEAD、index、worktree如果还是混可以记一个判断checkout 改“我现在站在哪里”reset 改“当前分支指到哪里”。源码里怎么落地mini-git 里相关文件主要是src/commands/cmd_checkout.csrc/commands/cmd_reset.csrc/core/ref.csrc/core/tree.csrc/core/index.ccheckout 的关键逻辑是解析目标如果是分支准备让 HEAD 指向 refs/heads/xxx如果是 commit准备进入 detached HEAD检查 index/worktree 是否干净用目标 commit 的 tree 恢复工作区和 index更新 HEADreset 的关键逻辑是解析目标 commit根据模式决定是否读取目标 treesoft 不动 index/worktreemixed 用目标 tree 重建 indexhard 用目标 tree 重建 index 并恢复 worktree最后移动当前分支引用这里有一个细节mini-git 的reset --mixed/--hard是先同步状态成功后再移动引用。这样如果恢复 index/worktree 失败不会出现“分支已经挪了但文件没恢复成功”的半截状态。面试里怎么说可以这样答checkout 更偏切换当前位置。checkout 分支时HEAD 会指向目标分支同时 index 和 worktree 会恢复成目标分支对应 commit 的 treecheckout 某个 commit 时会进入 detached HEAD。reset 更偏移动当前分支指针soft/mixed/hard 的区别在于是否同步 index 和 working treesoft 只动分支mixed 动分支和 indexhard 三者都动。如果追问为什么 checkout/reset 容易丢修改可以补一句因为 checkout 和 reset --hard 都可能写 worktree。如果当前工作区有未提交修改目标快照又要覆盖同名文件就可能丢数据所以实现上必须做安全检查或要求用户明确使用 hard。总结这一篇抓住五句话就够了checkout branch是换分支入口。checkout commit是 detached HEAD。checkout会恢复 index/worktree所以要保护本地修改。reset是移动当前分支指针。soft/mixed/hard的区别就是影响范围从 HEAD 扩大到 index再扩大到 worktree。理解这几个点checkout、reset就不再是“玄学撤销命令”而是几个明确状态之间的移动。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →