Emacs 31 Does Git Worktrees Natively: How My vc Setup Compares
Well this is awkward timing. No sooner had I finished bolting worktree management onto built-in vc than I installed the bleeding edge and found Emacs 31 went and did it properly. Right there in the 31 NEWS: a whole C-x v w working-tree map. So before I get too attached to my own code I thought I had better do an honest head-to-head. Verdict up front: I am keeping mine, with one key moved. Here is why.
First, what 31 actually ships under C-x v w:
w c(vc-add-working-tree) prompts for a directory, then a branch, but you have to create a branch for yourself, where as my version and also Magit ask for the name of the branch to create, as when creating a new worktree, wouldn't you always want to create a branch? Also with the built-in there is no placeholder initial name, with something involving the branch name, so this command currently feels a little disjointed.w w(vc-switch-working-tree) jumps to the same file in another tree. Lovely idea, completely different from my switch-to-vc-dirand I'm not quite sure if I really care about switching file contexts, I think I just really want to jump to the top levelvc-dirw k(vc-kill-other-working-tree-buffers) kills buffers visiting this file elsewhere. Read that twice: it does not delete the tree, despite the letter. So these commands are really very much file-centric. Again I would rather this delete the worktree.w s(vc-working-tree-switch-project) isproject-switch-projectlimited to trees sharing the same backing repo, with a matchingproject-find-matching-bufferhelper for hopping between same-named files, for this I would probably generally be more specific.w aandw A(vc-apply-to-other-working-treeandvc-apply-root-to-other-working-tree) copy a fileset's changes - or the whole tree's - into another tree, and with a prefix argument move them. This is the Transplant mechanic and it is genuinely something mine cannot do, do I need it, not sure, but I have logged it into my noggin.w x(vc-delete-working-tree) deletes the directory outright (to trash) and prunes. No--forcedance because there is nogit worktree removeinvolved at all, which also means it works on ancient gits.w R(vc-move-working-tree) relocates, and the implementation is cleverer than mine: on recent git it renames the directory and runsworktree repair, which means it can even move the main tree, something plaingit worktree moveflatly refuses. Older gits fall back throughworktree moveto a clean version error. A little ongit worktree repair, the .git file contains a repo reference and is backtraced through the original repo, the repair just reset both aspects which is very useful when moving worktrees around and also in that small case where you are running a shared folder repo over SMB, to get the worktree to work in both linux and maybe a host on windows!!
Now the head-to-head, feature by feature. Creating: theirs prompts bare directory then branch to use, but the branch must already be created, mine offers a project-branch sibling default and lands you in vc-dir.
Switching: theirs is file-grained, mine is status-buffer-grained - complementary, not competing. Listing: theirs has no list command at all beyond completion prompts (though the backend exposes vc-git-known-other-working-trees for lisp), while my Z l buffer stays.
Pruning: no built-in command, mine stays. Moving: theirs is strictly better, full stop.
Deleting: theirs is safer on old gits and trashes instead of destroying; mine understands --force and rescues a vc-dir sitting inside the dead tree.
So much for behaviour; now keys, because this is where it bit me. My maps live on Z (worktrees, Magit-style). Against 31's C-x v w map: w k kills buffers where mine removes trees, and w s switches projects where mine opens vc-dir - same letters, different continents. That settled it: the Z prefix stays, precisely because w is now taken with conflicting semantics, and note vc-dir itself has no built-in worktree keys at all, so Z there is uncontested territory.
What I am stealing, or at least circling: the prune-before-listing habit is obviously right, the trash-first delete is kinder than --force bravado, and the repair-based move handling the main tree is something git worktree move simply cannot do. The changeset transplant (w a) is the one I may just use as-is rather than reimplement - some things are better borrowed than rebuilt. What I am not giving up: the vc-dir-first workflow, the sibling defaults, the list buffer.