open source annoyances and affordances

As is my wont, I upgraded my installed Emacs packages this Saturday morning. When I restarted Emacs afterwards, everything broke. I’ve gotten the problem fixed, but I ran into some frustrations along the way.

My problems began immediately after the restart. I use desktop-save-mode, which means the files I previously had open, automatically opened back up when Emacs started. Additionally, I also got the following stack trace:

Debugger entered--Lisp error: (file-missing "Opening input file" "No such file or directory" "/Users/genehack/proj/apollo/models/.git")
insert-file-contents("/Users/genehack/proj/apollo/models/.git")
project-try-vc("/Users/genehack/proj/apollo/models/")
run-hook-with-args-until-success(project-try-vc "/Users/genehack/proj/apollo/models/")
project--find-in-directory("/Users/genehack/proj/apollo/models/")
project-current()
eglot--maybe-activate-editing-mode()
run-hooks(change-major-mode-after-body-hook after-change-major-mode-hook)
normal-mode(t)
after-find-file(nil nil)
find-file-noselect-1(#<buffer index.js> "~/proj/apollo/models/index.js" :nowarn nil "~/proj/apollo/models/index.js" (8667578097 16777222))
find-file-noselect("/Users/genehack/proj/apollo/models/index.js" :nowarn)
desktop-restore-file-buffer("/Users/genehack/proj/apollo/models/index.js" "index.js" nil)
desktop-create-buffer(208 "/Users/genehack/proj/apollo/models/index.js" "index.js" js2-mode (eldoc-mode override-global-mode gcmh-mode global-auto-revert-mode company-mode flymake-mode projectile-mode aggressive-indent-mode flycheck-mode smartparens-mode prettier-js-mode) 1 (nil nil) nil nil ((buffer-display-time 24256 19314 922097 0) (buffer-file-coding-system . undecided-unix)) ((mark-ring nil)))
eval-buffer(#<buffer *load*> nil "/Users/genehack/.emacs.d/.emacs.desktop" nil t) ; Reading at buffer position 9385
load-with-code-conversion("/Users/genehack/.emacs.d/.emacs.desktop" "/Users/genehack/.emacs.d/.emacs.desktop" t t)
load("/Users/genehack/.emacs.d/.emacs.desktop" t t t)
desktop-read()
#f(compiled-function () #<bytecode 0x1fe27544cc95>)()
run-hooks(after-init-hook delayed-warnings-hook)
command-line()
normal-top-level()

As you can imagine, this did not substantially improve my Saturday morning. Out of irritation, I started trying to figure out what had broken. The problem at the top of the stack trace is that Emacs is trying to open a file that doesn’t exist: /Users/genehack/proj/apollo/models/.git.

Now, it makes sense that that particular file doesn’t exist, because a .git directory is only found in the root of a project managed by Git — which in this case is /Users/genehack/proj/apollo. Looking a little further down the stack trace, I notice this function being called:

project-try-vc("/Users/genehack/proj/apollo/models/")

Since the argument to that function plus .git/ is the same as the non-existant file causing the error at the top of the stack trace, it seems like project-try-vc is a likely place to start looking for the real issue.

Looking into the definition of that project-try-vc function, I can see it’s defined in a file called project.el …but it’s undocumented. Pulling up the package index with M-x list-packages and searching for project, leads me to that library’s home page. Pulling that up, I can see that the most recent version — which is the one I’ve got installed — was released just yesterday.

At this point I’m pretty convinced the problem is this package. In order to confirm that, I downgrade to the previous version — but the problem persists. Downgrading to the 0.1 version, however, does fix the problem.

Looking at a diff between the 0.1 and the 0.1.2 version of the project.el file, I can see — consistent with the idea that the bug was introduced by the upgrade — that all the differences are in the project-try-vc function I suspect is causing the error:

4c4
< ;; Version: 0.1
---
> ;; Version: 0.1.2
277,278c277,294
< (vc-file-setprop dir 'project-git-root
< (vc-find-root dir ".git/"))))
---
> (let* ((default-directory dir)
> (root (vc-root-dir))
> (gitfile (expand-file-name ".git" root)))
> (vc-file-setprop
> dir 'project-git-root
> (cond
> ((file-directory-p gitfile)
> root)
> ((with-temp-buffer
> (insert-file-contents gitfile)
> (goto-char (point-min))
> (looking-at "gitdir: [./]+/\.git/modules/"))
> (let* ((parent (file-name-directory
> (directory-file-name root)))
> (default-directory parent))
> (vc-root-dir)))
> (t root)))
> )))

Breaking that down took a little noodling. I started out looking at these lines, which are declaring some variables and assigning values to them:

(let* ((default-directory dir)
(root (vc-root-dir))
(gitfile (expand-file-name ".git" root)))

That’s clearly where the problematic .git bit is getting added, and it seems like part of the point of this code is to define the root level of a project.

Looking at the previous version (at the top of the diff), you can see it’s finding the root a different way: using the vc-find-root function. Let’s see what happens if we make this change to the 0.1.2 version of the package, to restore the old way of finding the project root:

278c278
< (root (vc-root-dir))
---
> (root (vc-find-root dir ".git/"))

That change fixes the issue! Great! Let’s file a bug and then submit the fix for it… except… the project.el file contains absolutely no information about how to submit bugs or patches. There’s nothing on the project.el web page. No websites, no bug tracker, no email addresses.

Folks, if you maintain Open Source software and you ever wonder why you’re not getting contributions… this is why you’re not getting contributions: because you don’t spend any time thinking about making it easy (or in this case, even possible) for folks to contribute back to your project.

So, instead of supplying a bug report and a patch, so that other folks might avoid this problem when they upgrade their Emacs packages (and instead of working on PROJECT APOLLO, like I planned to do today), I’m writing this blog post. Hopefully, after I post this online in a few places, Cunningham’s Law will kick in and somebody will show up and tell me where I can send this patch after all…