DEVELOPER DESK阅读地图
9个主要章节
4段代码示例
6组图片与图表

先定位当前任务,再对照操作前提、结果与容易混淆的边界。

章节速览9 节 · 展开选择

正在写一个功能,暂存区里已有一部分准备提交的修改,这时又需要检查另一个版本。只切换分支可能打断当前工作;复制整个目录又容易混淆哪份修改属于哪个仓库。

Git worktree提供的是同一仓库的多个工作区:文件与暂存区可以分开,提交对象和普通分支仍然共享。这篇接续Git差异审查,通过一个保留全部实验目录的小仓库,实际核对“哪些独立、哪些共享”,再用脱离HEAD的工作区审查固定提交。

操作前先认清命令作用在哪个目录

每条Git命令都使用-C明确工作区。主工作区保持已暂存与未暂存修改,另开目录处理新分支。

审查时固定对象,再解释看到的内容

用完整提交ID创建脱离HEAD的审查目录。固定版本不等于只读,也不等于已经通过构建。

一个仓库,为什么可以有多个HEAD

先回想工作区、暂存区与提交:文件是你正在编辑的内容,索引决定下一次提交的快照,HEAD说明当前检出的分支或提交。多工作区让每个目录分别拥有这些当前状态,并通过仓库公共目录共享对象库与普通分支引用。

内容 主工作区与链接工作区的关系 操作上的含义
工作区中的文件 各目录独立 在topic中编辑,不会自动改写main目录的同名文件
索引,也就是暂存区 各工作区独立 在topic中add,不会替main暂存
HEAD与HEAD引用日志 各工作区独立 两处可以指向不同分支;各自记录切换过程
提交、树、blob等对象 同一仓库共享 topic中提交的新对象,可从main目录直接show
refs/heads下的普通分支 共享 topic分支推进后,各工作区查询该分支会看到同一新位置
仓库公共config 默认共享 修改local配置可能影响其他工作区,不等于目录私有设置

这里的共享规则来自官方worktree手册。不要把“所有引用都共享”当成规则:refs/bisect、refs/worktree等有例外。日常核对普通分支与HEAD已经足够,本文不直接编辑内部引用文件。

main、topic和review三个工作区各有文件、索引与HEAD,共享提交对象和普通分支引用,review的HEAD直接指向固定提交
图1|原创结构图。三个目录属于同一仓库;图中的公共区是逻辑关系,不承诺每台机器使用完全相同的物理路径。查看原图

命令1:建立一个全新的教学仓库

下面适用于PowerShell和支持worktree的Git;本地验证版本为Git 2.41.0.windows.3。先选一个可写的练习目录作为当前位置,命令会在其中建立带随机名称的新子目录。所有变量在同一PowerShell会话中继续使用。每条命令执行后先确认成功;失败时停止,不把剩余命令直接粘贴运行。

命令1|新建而非覆盖:仅用于新生成的教学目录,不对已有项目执行初始化。提交使用命令级教学身份,不修改全局配置;示例不需要远端、凭据或推送。

1
2
3
4
5
6
7
8
9
10
11
$demoRoot = Join-Path (Get-Location) ('worktree-demo-' + [Guid]::NewGuid().ToString('N'))
$demoMain = Join-Path $demoRoot 'main'
$demoTopic = Join-Path $demoRoot 'topic'
$demoReview = Join-Path $demoRoot 'review'
New-Item -ItemType Directory -Path $demoRoot
git init -b main $demoMain
New-Item -ItemType File -Path (Join-Path $demoMain 'notes.txt') -Value "base=A`n"
git -C $demoMain add -- notes.txt
git -C $demoMain -c user.name=WorktreeDemo -c user.email=demo@example.invalid -c commit.gpgsign=false commit -m 'A: base'
$demoBase = git -C $demoMain rev-parse --verify 'HEAD^{commit}'
git -C $demoMain status --short

把第一次提交称为A。正常情况下最后一次status没有修改行。$demoBase是本次实际生成的完整对象ID;创建时间、换行与对象格式都可能影响ID,所以文章不用一个固定哈希冒充所有读者的输出。

命令2:让main保持两层修改,再开topic

为了观察索引独立性,先在main追加一行并暂存,再追加另一行但不暂存。此时同一个文件在索引和工作区中各有一层变化。随后以已核对的A为起点,显式新建分支codex/demo-topic。

1
2
3
4
5
6
7
8
Add-Content -LiteralPath (Join-Path $demoMain 'notes.txt') -Value 'main-staged=B'
git -C $demoMain add -- notes.txt
Add-Content -LiteralPath (Join-Path $demoMain 'notes.txt') -Value 'main-unstaged=C'
git -C $demoMain diff --cached -- notes.txt
git -C $demoMain diff -- notes.txt
git -C $demoMain worktree add -b codex/demo-topic $demoTopic $demoBase
git -C $demoMain worktree list --porcelain
git -C $demoTopic status --short

main的短状态应为MM notes.txt:第一列M表示索引相对HEAD变了,第二列M表示文件相对索引又变了。topic则从提交A检出干净快照,不会继承main尚未提交的两行文字。新工作区的来源是指定提交,不是另一个目录当前肉眼可见的文件内容。

位置 HEAD所代表的已提交内容 索引中的notes.txt 工作区中的notes.txt
main A:只有base=A base=A加main-staged=B 再加main-unstaged=C
topic刚建立 A:只有base=A 只有base=A 只有base=A
自检1:main有未提交修改,topic一定会带上吗?

不会。本文显式从已提交的demoBase创建工作区;main的索引和文件变化留在main。若需要把其中的改动带到topic,必须另行设计并审查转移方式,不能假设worktree自动复制未提交内容。

命令3:在topic提交,回main核对共享对象

1
2
3
4
5
6
7
8
Add-Content -LiteralPath (Join-Path $demoTopic 'notes.txt') -Value 'topic=D'
git -C $demoTopic add -- notes.txt
git -C $demoTopic -c user.name=WorktreeDemo -c user.email=demo@example.invalid -c commit.gpgsign=false commit -m 'D: topic change'
$demoTip = git -C $demoTopic rev-parse --verify 'HEAD^{commit}'
git -C $demoMain show --no-patch --format='%H %P' $demoTip
git -C $demoMain show "${demoTip}:notes.txt"
git -C $demoMain diff --cached -- notes.txt
git -C $demoMain diff -- notes.txt

把新提交称为D,它的父提交是A。从main目录执行show能够读到D,且文件包含topic=D,说明对象库确实共享。与此同时main的两层diff仍分别显示B和C,没有被topic的add或commit替换。

分支codex/demo-topic指向D,main仍指向A。使用不同工作区不会自动合并分支;“对象已经能读到”与“我的分支已经包含它”是两件事。审查提交内容可以用show,判断祖先关系还应使用merge-base等对应命令;不能拿同一仓库中对象存在,代替集成状态检查。

同一分支为什么会拒绝被再次检出

普通情况下,Git会拒绝把已经被某个工作区检出的分支再用于另一个工作区。否则两个不同索引与文件状态都以同一个可移动分支为当前分支,一个目录提交推进分支后,另一个目录的HEAD基准也跟着变化,容易产生难以理解的状态。

在本例中,codex/demo-topic已由topic占用。只读检查可先用git worktree list --porcelain定位;验证程序也实际尝试了不带强制选项的重复add并检查非零退出、原工作区状态与分支位置未变。面对占用拒绝,先选择新的任务分支或脱离HEAD审查,而不是把强制选项加上去使报错消失。

自检2:同一提交能出现在多个工作区吗?

能。这里受保护的是分支占用,不是提交对象只能检出一次。多个脱离HEAD的工作区可以检查同一提交;新分支也可以从同一提交开始,各自拥有独立的分支名字与后续移动。

命令4:用固定提交建立审查工作区

命令4|脱离HEAD审查:这里的demoTip必须已成功解析并核对为D。新目录固定检出这个提交,不占用codex/demo-topic;它仍是可写目录。

1
2
3
4
5
6
7
git -C $demoMain worktree add --detach $demoReview $demoTip
git -C $demoReview rev-parse --verify 'HEAD^{commit}'
git -C $demoReview status --short --branch
git -C $demoReview diff $demoBase $demoTip -- notes.txt
git -C $demoReview rev-parse --show-toplevel
git -C $demoReview rev-parse --absolute-git-dir
git -C $demoReview rev-parse --path-format=absolute --git-common-dir

HEAD应解析成D,状态表明处于脱离HEAD,diff只显示topic新增的行。路径检查区分当前文件目录、该工作区的Git私有管理目录和仓库公共目录;这些命令的含义见文献2。链接工作区根部的.git通常是指向管理位置的文件,不应把它误当成损坏仓库或手工改写它。

main分支留在A,topic分支从A到D并可继续到E,review的脱离HEAD固定指向D,不随topic分支推进到E
图2|原创引用关系图。D、E为教学提交标签;review固定D,不能据此声称文件不可修改或测试结果已复核。查看原图

若topic之后继续提交E,review仍指向D,直到你在review显式执行切换、提交等操作。固定对象ID解决的是版本漂移;它没有冻结系统依赖、数据库、环境变量或远端接口。要比较两个版本的行为,还需要对齐这些外部条件并记录实际测试结果。

在review里也能修改、暂存、提交。脱离HEAD的提交不会自动推进codex/demo-topic;如果确实要保留新的审查修改,应先核对当前对象,再新建明确分支。可参考reflog与提交找回理解可移动日志,但不要把日志可能存在当作日常保存策略。

多开目录后,哪些问题仍需自己处理

仓库配置默认共享。git config --local的local是仓库级,不等于当前工作区私有。Git提供extensions.worktreeConfig与worktree配置机制,但启用前要核对版本、已有core.worktree/core.bare等设置和团队工具;本文不启用或迁移这些设置。语义见文献3。

构建依赖和缓存通常不是Git对象。如果它们位于各自目录中,可能各有一份;如果工具写入同一个全局缓存、共享数据库或端口,仍会相互影响。忽略规则也不会自动把主目录的未跟踪文件复制过来。启动服务前核对端口、输出目录和配置路径,比看到两个文件夹就假定完全独立可靠得多。

工作区列表是一张仓库管理记录,不是磁盘备份。本文实验保留main、topic、review及所有管理元数据,未执行目录移除或清理。真实项目结束任务时,应先审查未提交、未跟踪、忽略文件及仅在脱离HEAD可达的提交,再按团队的目录生命周期规则处理;不要直接删目录后把管理记录不一致当成Git失效。

自检3:review固定了D,测试是否就可重复?

只固定了Git提交。依赖版本、数据、运行时、端口和外部服务仍可能变化;审查记录需要同时保存环境与实际结果。worktree本身不提供容器、进程或网络隔离。

自检4:topic的提交已能从main读取,是否等于已经合入main?

不等于。对象库共享使对象可读,但main分支仍留在A。需要另外核对祖先与分支关系,再按项目流程集成;目录数量也不代表分支数量或已发布版本数量。

实验能证明的范围与下一步

本篇验证在新建的独立教学仓库内运行真实Git,核对对象父关系、分支占用、三个索引与文件内容、main两层diff不变、公共目录一致与私有目录不同。还在topic继续提交E,确认review仍为D,并创建脱离HEAD提交以核对普通分支未移动。所有实验产物保留,不接触博客部署仓库。

它证明的是当前本地Git版本下这组操作的具体语义,不能代替子模块、稀疏检出、网络盘或大型依赖项目的专项验证。需要解释变化从哪个版本开始,可衔接Git bisect;要撤销已发布改动,则继续读Git revert。

参考文献与对应位置

  1. Git官方:git-worktree,2.39.0手册条目(适用于本文验证的2.41系列基础命令)。 DESCRIPTION、add、REFS与DETAILS。核对链接工作区、独立HEAD/索引、共享普通分支及占用拒绝;本文命令输出另由本地实验核对。返回命令1。
  2. Git官方:git-rev-parse,2.41.0。 路径和修订解析选项。用于绝对管理路径、公共目录与提交对象核对。返回命令4。
  3. Git官方:git-config,2.41.0;git-worktree配置说明。 extensions.worktreeConfig、工作区配置边界。用于默认共享与可选扩展的区别;本文不调整已有仓库配置。