Quickly switching between git status files buffers in Emacs

Cover Image for Quickly switching between git status files buffers in Emacs
Rahul M. Juliato
Rahul M. Juliato
#emacs#git# workflow

When I'm deep into developing a feature, I don't want to think about whether a file is already open in a buffer, or if I need to search the whole project to find it.

I just want to jump straight to the files I'm actively working on, the ones that Git tells me are modified, untracked, or even renamed.

Emacs already gives us great defaults for different contexts:

C-x b (switch-to-buffer) → jump between open buffers.

C-x p b (project-switch-to-buffer) → jump between buffers in the current project.

C-x p f (project-find-file) → find any file inside the project.

Each of these has its value and time. But in the middle of a coding session, when iterating fast on a feature, none of them give me what I want: a list of just the files I've touched.

That's the itch that led me to write this command.

The Function and Binding

(defun emacs-solo/switch-git-status-buffer ()
  "Parse git status from an expanded path and switch to a file.
The completion candidates include the Git status of each file."
  (interactive)
  (require 'vc-git)
  (let ((repo-root (vc-git-root default-directory)))
	(if (not repo-root)
		(message "Not inside a Git repository.")
	  (let* ((expanded-root (expand-file-name repo-root))
			 (command-to-run (format "git -C %s status --porcelain=v1"
									 (shell-quote-argument expanded-root)))
			 (cmd-output (shell-command-to-string command-to-run))
			 (target-files
			  (let (files)
				(dolist (line (split-string cmd-output "\n" t) (nreverse files))
				  (when (> (length line) 3)
					(let ((status (substring line 0 2))
						  (path-info (substring line 3)))
					  ;; Handle rename specially
					  (if (string-match "^R" status)
						  (let* ((paths (split-string path-info " -> " t))
								 (new-path (cadr paths)))
							(when new-path
							  (push (cons (format "R %s" new-path) new-path) files)))
						;; Modified or untracked
						(when (or (string-match "M" status)
								  (string-match "\?\?" status))
						  (push (cons (format "%s %s" status path-info) path-info) files)))))))))
		(if (not target-files)
			(message "No modified or renamed files found.")
		  (let* ((candidates target-files)
				 (selection (completing-read "Switch to buffer (Git modified): "
											 (mapcar #'car candidates) nil t)))
			(when selection
			  (let ((file-path (cdr (assoc selection candidates))))
				(when file-path
				  (find-file (expand-file-name file-path expanded-root)))))))))))

(global-set-key (kbd "C-x C-g") 'emacs-solo/switch-git-status-buffer)

The command parses git status --porcelain=v1, collects the interesting files, and offers them through completing-read. This means it works with whichever completion frontend you already use, and it switches to the selected file's buffer (opening it first if it isn't already open).

Completion in Action

Here's how it looks in different completion UIs:

Plain completion buffer completion-example

Fido completion-example

Fido vertical completion-example

Icomplete vertical completion-example

Vertico completion-example

No matter which you prefer, you get a filtered list of just the files that matter right now.

Why emacs-solo?

This command is part of my emacs-solo project, a collection of Emacs enhancements that stick to built-in tools and minimal glue code.

The goal is to show how far we can go without depending on a huge package ecosystem, while still making day-to-day workflows smoother.

Conclusion

When I'm developing a feature, I care about the files I've just modified, created, or moved.

This command scratches exactly that itch: fast navigation to Git-relevant files, without worrying if they're open or buried somewhere in the project tree.

Give it a try, and check out emacs-solo for more small, practical commands like this one. 🚀

Edit:

2025-09-04: Fixed the global binding to emacs-solo/switch-git-status-buffer, as pointed by u/jplindstrom on r/emacs (reddit).