A surprising upside to vc-dir marks over magit

vc.el is Emacs’s built-in interface for version control CLI tools. With respect to Git, magit has for several years now been the most favored (with respect to popularity) Git1 interface. At least in the r/emacs subreddit, magit is frequently considered a killer-feature—an irreplaceable feature unique to Emacs.

For the most part I am inclined to agree. However, in the last few months, I’ve been regularly experimenting using vc. This is in part motivated by recent improvements to vc’s implementation of Git, and another part motivated by a curiosity to see whether I’d end up enjoying some parts of the vc-way of doing things over the magit-way of doing things.2

On such insight has been the surprising benefits of calling vc-dir instead of magit-status.

The nicety of vc-dir marks

In magit-status, I would typically stage various files and hunks and commit them when ready. vc does not use Git’s “staging” concept (because not all version control systems use such a concept). So instead, to achieve something analogous, a user might call vc-dir (or, much more commonly for me, project-vc-dir), mark edited files (or entire directories!) using vc-dir-mark (m), and check out (commit) those changes via vc-next-action (v) or see a diff of those changes with vc-diff (=) and commit the changes in that diff-mode buffer (v).3

A problem for the magit interface, however, is visual clarity when it comes to focusing on changes in a certain subset of files. For example, sometimes I am working with changes in files within a single directory alongside many other uncommitted changes elsewhere in the repository. To make a commit with magit, I would need to navigate past or fold changed hunks/files I want to ignore. With many unstaged changes this means many files to ignore. This leads to a significant reduction in visual clarity since I end up having to ignore more files than I’m currently working on.

There is a subtle solution in vc.el. The secret lies in the fact that marks, which can be used to selectively create diff buffers and commit changes from those buffers, are preserved in vc-dir buffers after changes are committed. A mark is only removed once the corresponding line is removed via something like vc-dir-hide-up-to-date (x) or vc-dir-kill-line (C-k). Marks are magical because they let me focus on the files relevant to my current work: I can mark the files and directories I am focusing on then call vc-next-action in the vc-dir buffer to commit changes only in those files and directories (or, if I want to be selective about the hunks I commit: first call vc-diff to see the diff of just the marked files, kill/remove the hunks I don’t want to commit, then vc-next-action).Then I can write more changes and make more commits in the same way. (Furthermore, while this vc workflow might seem like it would necessarily involve many more keystrokes, I found this not to be the case under certain scenarios.)

Effectively, in short, while magit can sometimes become visually cluttered, vc-dir marks let users focus on their current work by easily excluding from view files and directories one is not concerned with currently.

A caveat

Historically, I’ve found this especially fruitful when working on my literate Emacs config4 (wherein any “single change” involves changes to the .org file as well as the resulting tangled file(s)) and Emacs packages. Still, I think I prefer magit in cases where the staging functionality of Git is crucial. For instance, I have a repository that houses all my org notes and org-agenda files. There are frequently many changes for this repository, so the convenience of staging/unstaging has been crucial for me. In cases like these, I haven’t found an equivalent for vc.el yet.


  1. magit is an interface for Git, whereas vc.el is an interface for other version control tools, albeit older ones, some of which may follow a non-decentralized version control paradigm (read the Emacs Info node (emacs) Version Control Systems for more information):

    • CVS
    • Subversion (svn)
    • SCCS
    • CSSC
    • Mercurial (hg)
    • Bazaar (bzr)
    • SRC (src)
     ↩︎
  2. A philosophical tangent: the interface of a tool (digital or analog/physical) influences how users interact with that tool and how users accomplish the purposes of the tool. As such, what seems at first as a subtle difference in workflow or interface to achieve the same end almost always overlooks aspects relevant to the workflow’s effectiveness.

    For instance, users of Emacs will readily recognize that Emacs is thoroughly characterized by an “emacs-y” way of doing things. The total network of keybinds, conventions, and procedures of using Emacs might accomplish the same purposes as other software (combined or standalone), but this specific network of “ways to interface with Emacs-as-a-tool” enables Emacs to offer a uniquely effective experience. ↩︎

  3. If I want to commit changes with much fewer unstaged changes present, then a faster method would be: call vc-root-diff (C-x v D), modify the diff-mode buffer accordingly with diff-hunk-kill (k) or diff-delete-other-hunks (C-c RET n; a recent addition to Emacs-30), then commit the changes with vc-next-action (v).  ↩︎

  4. See also footnote 3. ↩︎