Git 的设计部分目标就是支持试验。只要你知道自己的工作正在被安全地跟踪,并且可以在出现严重问题时回退到安全状态,你就无所畏惧地去尝试新的想法。然而,创新的代价之一就是——在此过程中你很可能会搞得一团糟。文件会被重命名、移动、删除、修改、切割,新增文件,甚至一些你不打算跟踪的临时文件也会在工作目录中占据一席之地。
简而言之,你的工作区就像是一座纸牌屋,在“差不多行了!”和“糟糕,我都干了些什么?”之间摇摇欲坠。那么,当你需要在某个下午把仓库恢复到已知状态,好好继续“正经”工作时,该怎么办?经典命令 git branch
和 git stash 会立刻浮现脑海,但它们都不擅长处理未跟踪文件,对路径变动和其它重大变动也很难正确“储藏”当前工作。答案是:使用 Git worktree。
什么是 Git worktree
Git worktree 是与你的主仓库关联的副本,允许你同时检出多个分支。每个 worktree 都有独立的路径,可以处于不同的状态并检出不同的分支。使用新的 worktree 的好处在于,你可以在不干扰当前工作环境的前提下,去做一些与当前任务无关的改动,提交这些改动,然后在稍后合并它们。
git-worktree
手册页中给出的标准示例是:你正在为一个项目开发激动人心的新功能,这时项目经理告诉你有一个紧急修复要做。可你的当前仓库(即“工作树”)正因开发重大新功能而混乱不堪。你不想把这个修复“悄悄”塞入当前的迭代中,也不愿意先 stash 改动再去创建新分支来做修复。于是,你决定创建一个干净的 worktree 来专门做这个修复:
$ git branch | tee
* dev
trunk
$ git worktree add -b hotfix ~/code/hotfix trunk
Preparing ../hotfix (identifier hotfix)
HEAD is now at 62a2daf commit
此时,在你的 code
目录下多了一个名为 hotfix
的文件夹,它是与你的主项目仓库关联的一个 Git worktree,且其 HEAD
已指向名为 trunk
的分支。你可以像在主工作区一样对它进行操作:进入该目录、做紧急修复、提交,然后在完成后删除该 worktree:
$ cd ~/code/hotfix
$ sed -i 's/teh/the/' hello.txt
$ git commit --all --message 'urgent hot fix'
修复完成后,你可以回到之前的任务,完全由你决定何时将热修复合并到主项目中。例如,你可以直接从该 worktree 向远程仓库推送更改:
$ git push origin HEAD
$ cd ~/code/myproject
或者将该 worktree 打包为 TAR 或 ZIP 文件归档:
$ cd ~/code/myproject
$ git archive --format tar --output hotfix.tar master
或者在本地从独立的 worktree 中拉取更改:
$ git worktree list
/home/seth/code/myproject 15fca84 [dev]
/home/seth/code/hotfix 09e585d [master]
然后,你可以根据团队需要,选择合适的策略来合并这些更改。
列出活动的 worktree
使用 git worktree list
命令可查看所有 worktree 及其检出的分支:
$ git worktree list
/home/seth/code/myproject 15fca84 [dev]
/home/seth/code/hotfix 09e585d [master]
无论你在任何一个 worktree 中执行此命令,都能看到同样的列表。只要不手动移动或者删除关联信息,worktree 就始终和主仓库保持链接。
移动一个 worktree
Git 在项目的 .git
目录中跟踪每个 worktree 的位置和状态:
$ cat ~/code/myproject/.git/worktrees/hotfix/gitdir
/home/seth/code/hotfix/.git
如果需要重定位某个 worktree,务必使用 git worktree move
,否则 Git 在更新该 worktree 状态时会失败:
$ mkdir ~/Temp
$ git worktree move hotfix ~/Temp
$ git worktree list
/home/seth/code/myproject 15fca84 [dev]
/home/seth/Temp/hotfix 09e585d [master]
删除一个 worktree
当你完成该工作后,可以用 remove
子命令将其移除:
$ git worktree remove hotfix
$ git worktree list
/home/seth/code/myproject 15fca84 [dev]
要保持 .git
目录的整洁,在删除之后还可以运行 prune
子命令:
$ git worktree prune