zunzuncito

For whatever reason I’ve been uncovering software bugs at an unprecedented rate in the past 10 days. This is by no means a bad thing, I enjoy hunting down and fixing bugs, but it does mean that the additional overhead of drafting a post about each bug becomes a bit too much. So instead here’s a quick overview - the linked patches and merge requests will have more information, if you are interested.

Trash size calculation in KIO

I noticed this one pretty much right after starting to use Dolphin but did not end up looking into it until quite a bit later: when displaying the size of the items in the trash, the application would always show 0 bytes. This would also cause the automated cleanup of items to fail - Dolphin simply believed that the trash was empty.

KDE uses the KIO framework to provide management of the trash. A recent commit had changed the construction of a QDirIterator in a way that would make it ignore all items when iterating over the trash directory. Thankfully the fix was straightforward and it was merged quickly.

git-shortlog(1) segfaults outside of a git repository

This one I uncovered as I was writing a small script to give me an overview of commit authors in all the git repositories I had cloned locally. I was happily scanning through my source directory using the --author flag for git-shortlog(1) to generate this, fully expecting git to complain about the few non-git directories I had. Instead of complaints, however, I got a segfault.

Turns out that a change back in May stopped setting SHA1 as the default object hash. This was done to progress the slow-moving transition to stronger hash functions but inadvertently broke git-shortlog(1) whose argument parsing machinery expected a default hash algorithm to be set. I sent a patch upstream.

An infinite loop in plasmashell

I regularly use the Activities functionality in Plasma 6 and switch through my activities using Plasma’s built-in activity manager. A couple of days ago I managed to make plasmashell, the provider for Plasma’s desktop and task bar, freeze - I had hit the “up arrow” key in the activity filter text box when there were no results visible. This was perfectly reproducible, so I went to investigate.

The cause of the issue was a do-while construct not handling a specific sentinel value, making it loop infinitely. For this one I also opened a merge request upstream.

About a week ago I noticed that fd(1), a Rust-based alternative to find(1), would suddenly segfault on my musl-based server system. Usually a segfault is nothing particularly special to my eyes, but this one was different. Even just having fd(1) attempt to print its help text was enough to trigger it, and when I attempted to debug it with gdb(1), I saw the following:

(gdb) run
Starting program: /usr/bin/fd

Program received signal SIGSEGV, Segmentation fault.
memcpy () at ../src_musl/src/string/x86_64/memcpy.s:18
warning: 18	../src_musl/src/string/x86_64/memcpy.s: No such file or directory
(gdb) bt
#0  memcpy () at ../src_musl/src/string/x86_64/memcpy.s:18
#1  0x00007ffff7ab7177 in __copy_tls () at ../src_musl/src/env/__init_tls.c:66
#2  0x00007ffff7ab730d in static_init_tls () at ../src_musl/src/env/__init_tls.c:149
#3  0x00007ffff7aae89d in __init_libc () at ../src_musl/src/env/__libc_start_main.c:39
#4  0x00007ffff7aae9c0 in __libc_start_main () at ../src_musl/src/env/__libc_start_main.c:80
#5  0x00007ffff74107f6 in _start ()

So… the segfault is in musl, not in fd!?

I immediately checked whether other basic programs on the system worked. They did. I checked when I last updated musl. A couple of months ago, so that can’t be it. I checked specifically whether another Rust-based program worked. It did.

fd(1) had been updated pretty recently, and I remembered it working correctly about a month ago, so maybe something specific to fd(1)’s usage of Rust triggered this segfault in musl? I wanted to make sure I could reproduce this in a development environment, so I cloned the fd(1) repository, built a debug release, and ran it…

It worked. Huh!?

I decided it was likely that portage, Gentoo’s package manager, was building the program differently, so I took care to apply the same build flags to the development build. And what can I say:

error: failed to run custom build command for `crossbeam-utils v0.8.20`

Caused by:
  process didn't exit successfully: `fd/target/[...]/build-script-build`
      (signal: 11, SIGSEGV: invalid memory reference)

… it didn’t even get to build the fd binary proper. A segfault again, too. What on earth was going on? Why didn’t this also happen in the portage build?

Thankfully I now had a reproducer, so I did the only sensible thing and started removing random build flags until I got fd to build again. This was our culprit:

-Wl,-z,pack-relative-relocs

Already pretty out of my depth considering the fact that I couldn’t fathom how fd(1) got musl to segfault on memcpy, I now also found that a piece of the puzzle required me to understand specific linker flags. Oof.

Unsure what to do next I decided on a whim to compare the working and the broken binary with readelf(1). The most obvious difference was that the working binary had its .rela.dyn relocation section populated with entries whilst the broken one was missing .rela.dyn but had .relr.dyn instead. At a loss, I stopped and went to do something else.

The story would probably have ended here had I not mentioned this conundrum to my partner later in the day. We decided to have another look at the binaries. After some discussion we determined that the working binary was dynamically linked whilst the broken one wasn’t. The other working Rust-based program, rg(1), was also dynamically linked and had been built a while ago, so at some point portage must have stopped producing Rust executables that were dynamically linked. Finally some progress!

At this point we need some background. Early on, Rust decided to use the x86_64-unknown-linux-musl target to provide statically-linked binaries that would run on a wide range of systems. Whilst support for dynamically linked executables on musl systems was added back in 2017, the default behaviour was never changed, so Gentoo has to make sure to disable static linking by passing the target-feature=-crt-static flag.

It does this in a system-wide fashion by setting an environment variable in /etc/env.d:

$ cat /etc/env.d/50rust-bin-1.80.1
LDPATH="/usr/lib/rust/lib"
MANPATH="/usr/lib/rust/man"
CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-C target-feature=-crt-static"

This setting should therefore be picked up by portage as well, but when I examined its build environment it was simply not there. So finally we come to the last piece of the puzzle: a recent change in how RUSTFLAGS are set within portage. Here’s the important part:

local -x CARGO_TARGET_"${TRIPLE}"_RUSTFLAGS="-C strip=none -C linker=${LD_A[0]}"
[[ ${#LD_A[@]} -gt 1 ]] && local CARGO_TARGET_"${TRIPLE}"_RUSTFLAGS+="$(printf -- ' -C link-arg=%s' "${LD_A[@]:1}")"
local CARGO_TARGET_"${TRIPLE}"_RUSTFLAGS+=" ${RUSTFLAGS}"

Quoth the bash(1) manual:

Local variables “shadow” variables with the same name declared at previous scopes. For instance, a local variable declared in a function hides a global variable of the same name: references and assignments refer to the local variable, leaving the global variable unmodified.

When previously the RUSTFLAGS environment variable was only touched when cross-compiling, it was now overridden. To confirm, I edited the file in question to include the previous value, and both fd(1) and rg(1) worked again. Success!

This whole saga was also reported to the Gentoo bug tracker and promptly fixed. A project for another day is figuring out exactly how a change from static linking to dynamic linking causes segfaults like this, because I sure would love to know the details.

The freedesktop.org shared MIME-info database spec says the following in section 2.11:

A type is a subclass of another type if any instance of the first type is also an instance of the second. For example, all image/svg+xml files are also application/xml, text/plain and application/octet-stream files. Subclassing is about the format, rather than the category of the data (for example, there is no ‘generic spreadsheet’ class that all spreadsheets inherit from).

Some subclass rules are implicit:

  • All text/* types are subclasses of text/plain.
  • All streamable types (ie, everything except the inode/* types) are subclasses of application/octet-stream.

So far so good; this makes intuitive sense and seems sensible enough. There is an interesting consequence of this rule when the MIME-info database is used by desktop systems for file associations, however: An application associated with application/octet-stream will automatically be associated with all streamable types as well.

This means that if you associate application/octet-stream with your text editor, your desktop system will also suggest you open video and audio files with that same text editor. This behaviour can be quite surprising, especially if the association was added automatically when a file was opened through the “Open with…” dialog.

What is even more confusing if you don’t happen to know the subclassing rule is the fact that ~/.config/mimeapps.list and applications interfacing with this file will not even list the editor as associated with any audio or video files. You might just skip over the entry it has for application/octet-stream, not realizing its significance. Perhaps you even assume (understandably) that application/octet-stream only specifies any file of “unknown” type. User-facing documentation on desktop systems (if it even exists) does not discuss this behaviour.

Whilst looking into this I found an older KDE bug report with some interesting thoughts on how to explain this behaviour to the end user, but sadly as far as I have seen none of these have made it into the system setting’s file association dialog.

The file manager I’m using on my Plasma 6 system, Dolphin, has built-in support for remote folders via the KIO framework. Where before I was relying on sshfs mount points in /etc/fstab, I decided to try out the Dolphin way and set up my remote devices using its sftp backend.

After a couple of days now I can say that this works beautifully… until you want to access the remote device on something that does not interface with KIO. This is especially important for me (and others) since I want to be able to browse networked filesystems via my terminal and have the ability to directly open a terminal in that location through Dolphin, something which is not possible with the KIO backend.

So in the end I went back to mount points in /etc/fstab. One small problem remained, however, and that was the way those mount points were displayed within Dolphin. There seemed to be no way to customize a mount point’s name or icon, leading to an annoyingly long /home/wolf/net/hosts/coleridge entry in the Devices section of Dolphin’s places panel.

I couldn’t find any help in fstab(5), and indeed I had never heard of a way to give a mount point a “pretty name”. However, after a bit of searching, I found people offhandedly mentioning the x-gvfs-name option. Some more searching revealed that nobody seems to care about documenting these features, but I was finally able to find an authoritative source within gvfs itself.

Happily there’s not only x-gvfs-name but also support for custom icons through x-gvfs-icon. So, if you want your file manager to display a pretty name and icon for one of your mount points, simply add the following to the relevant entry in /etc/fstab:

x-gvfs-name=My%20Device,x-gvfs-icon=network-server

This should be possible at least on GNOME and KDE desktops. I imagine a bunch of other environments and programs silently support this behaviour as well.

A screenshot of Dolphin, KDE's
file manager, showing the user's home directory with the places panel on the
left side containing various categorized entries. The entries below the Devices
category read flood, demeter, coleridge, and OnePlus 12, each with its own
pretty name and relevant icon.
Dolphin displaying pretty names and icons in the Devices category

If you are like me then you have a basically never-ending backlog of videos to watch. Perhaps sometimes you even watch videos so long that they’re basically impossible to finish in one sitting. Or you have about an hour left on a video, but you should really go to sleep.

All of these require some way to remember the timestamp where you left off. I use mpv(1) to watch my videos and thankfully there is a built-in way to do this. By default, hitting Q will quit the video and remember the timestamp (among other settings) for next time you open the file. This works regardless of whether the video exists on your filesystem or is streamed from a URL.

After a while of using this you might amass more than a couple of unfinished videos that mpv(1) knows about, but what it sadly does not provide is an easy way to show you which videos those are…

Whilst you can set write-filename-in-watch-later-config and have mpv(1) save filenames and URLs in its watch later entries, quickly finding them is another story.

This is where a shared effort between my partner nortti and me comes in: later(1). It is a small Python script that parses mpv(1)’s watch later entries and displays them in a human-readable and pretty manner:

albatross ~$ later
Sep 19 19:50	https://www.youtube.com/watch?v=VKGtMK4CGV4	# The 2 Hour Hunt for Light Arrows - OoT Randomizer
Sep 19 22:30	/home/wolf/Mall Grab - Understand feat. Brendan Yates.webm

From here it is easy to open up the video you want to continue watching: simply paste the path or URL printed by later(1) into mpv(1).

You might notice that the YouTube video is commented with its title. This is not information that mpv(1) saves, but later(1) can help here too. Using the -u or --update-titles flag, it will extract video titles using yt-dlp(1) and save them in its cache for subsequent retrieval. This should work for any site that yt-dlp(1) itself supports.

If you have a Python available, setting up and using later(1) is trivial: simply copy the script into a directory in your $PATH (and the manual into $MANPATH) or use the Makefile:

$ PREFIX=~/.local make install

Do note that for later(1) to work correctly, the aforementioned write-filename-in-watch-later-config setting needs to be set.

For the last couple of months I have been running sway on my main desktop system after having been forced away from hikari because of its practically halted development and incompatibility with newer wlroots versions.

I never felt completely satisfied with it and the whole experience was rather joyless, so about a week ago I decided to give KDE Plasma 6 a try after a surprisingly decent experience on the KDE Neon live image.

Whilst undoubtedly greater in its complexity and code size than sway, to me Plasma 6 seems like one of the last decent desktop environments still remaining. It’s incredibly customisable (but still comes with good defaults), looks nice out of the box, and most importantly seems to care about providing a nicely integrated and featureful experience. This even includes a companion app on Android, KDE Connect. It remains to be seen whether it will fully convince me in the long run, but for now I am very satisfied with it.

A picture of the KDE Plasma 6 desktop
environment, with a browser window, a terminal, and an instance of Dolphin, a
file manager.
KDE Plasma 6 with a few windows open

This last week was mostly spent learning about the desktop environment and setting everything up exactly how I want it to be, but there were two notable bugs to squash as well.

The first one reared its ugly head once I enabled backwards-compatibility with Qt5-based apps. I have a couple of such apps still, most prominently Mumble and Quassel IRC. Once the latter was built against the KFramework libraries, no more notifications were shown…

Fixing this ended up taking about two days, most of which were spent discovering exactly how KNotifications work. KDE provides apps with a tighter integration to the notification service, allowing users to specify which types of notifications to show, and how. Applications specify their notifications by shipping an <app>.notifyrc file. KDE ties this file to the application by matching its base name to the name given to the application (usually through a call to QCoreApplication::applicationName or when creating KAboutData).

It turns out that Quassel had recently been patched to fix an issue where desktop environments did not show its icon correctly. This required a call to setDesktopFileName in KAboutData to make environments aware of the connection. However, Quassel’s application name was changed in the same commit, severing its link with the name given through its quassel.notifyrc file. This seems to have been done in addition to the setDesktopFileName call and was not necessary to solve the issue the commit was trying to address.

I prepared a pull request fixing this issue by reverting part of the offending commit.

A picture of a
notification from Quassel IRC saying 'yay for notifications'.
Glad to have these back

The second bug I randomly came across whilst perusing journalctl and seeing the following error from Dolphin, KDE’s file manager:

QString(View)::contains(): called on an invalid QRegularExpression object
(pattern is '\A(?:file:///home/wolf/[Z-A]/?)\z')

Seeing this immediately made me wonder whether Dolphin plugs a URL straight into a regular expression without escaping it, and the answer, of course, is yes. I spent most of today’s afternoon hunting this issue down and preparing a merge request that fixes it in an elegant way.

Cover art for Microgravity
Microgravity

Biosphere, Geir Jenssen’s moniker, is one you often see featured in various ambient music best-of lists. For some reason it has taken me until this last month to finally check out his early (and most well-regarded) work, prompted by a recommendation on Spotify of all places.

Beginning with 1991’s Microgravity I was quite surprised to find a more beat- and loop-heavy album. Jenssen interweaves simple and repetitive melodies with bass-heavy backing and unique samples from nature and science-fiction. The titular first track Microgravity, for example, pits a ringing phone against an icy howling wind, all whilst accompanied by a deep bass track that contrasts rather cold and industrial instrumentation.

Baby Satellite and Tranquilizer pick up the pace a bit and get a bit more sample-heavy, the latter containing a lovely vocal sample from Space: 1999. Listening to the album I couldn’t help being reminded of Woob’s early albums as Journeyman, Mama 6 and National Hijinx, which are similarly heavy on samples from pop fiction.

The album’s penultimate track Baby Interphase is probably my favourite one off of the original album. Its use of klaxons and the sound of what I can only describe as a “science-fiction beacon” in the main mix are refreshingly silly, whilst the rest of the instrumentation has a great space-y and floaty feel.

The album as linked above contains a second CD with unreleased tracks from the era, and those are not to be missed. Especially the upbeat Search with its unrelenting repetition of a vocal sample and Dewy Fields with its haunting trumpet-like cries.

Cover art for Substrata
Substrata

Jenssen’s third studio album, Substrata, was released in 1997. This is the first genuinely ambient album by Biosphere and one that usually ends up on best-of lists.

Right off the bat I do not have trouble seeing why. As the Sun Kissed the Horizon opens the album with field recordings of a far-away airplane contrasted against the frolicking of children in the background. It then seamlessly fades into the ambient tour de force which is Poa Alpina, an achingly beautiful composition of strums and thick almost foglike drone. Ended by sounds of downpour the track dissolves into Chukhung, a more alien and inquisitive experience that has a main sample endlessly rotate around the listener’s ears, as if searching for someone.

Vocal samples are present again as well, with The Things I Tell You containing an excerpt from The Giant’s speech in Season 2 of Twin Peaks and Hyperborea featuring Major Briggs from the same episode talking to his son, Bobby. Kobresia samples a radio broadcast of a documentary about Karl Nikolaev, a purported telepath trying to guess the nature of an item lying two floors away.

The album ends with Silene, an eerie and strangely nostalgic track that reminded me a bit of The Caretaker’s An Empty Bliss Beyond This World. There’s lots to love in this album, but I personally keep coming back to Poa Alpina as my favourite. I just can’t get enough of how painfully lonely it feels.

I have a pretty extensive music library that I manage with MPD, the Music Player Daemon. For the longest time now I have also been aware of beets, another management system for music libraries. I played around with it a few times but never took the plunge to have it organize my entire collection.

A few days ago, whilst looking up a particularly obscure recording, I ended up finding it on MusicBrainz and decided to give beets, which integrates very tightly with that service, another serious try.

Yesterday I finally completed a first rough import of my entire library (which encompasses about 20,000 songs in 1400 albums). Given the integration with MusicBrainz, I now try to map every album to a release in their database. If I can’t find it there, I instead fall back to an old favourite of mine, Discogs. beets will automatically update and correct any tags once I select the right release.

Whilst importing I decided that I should make more use of the “Grouping” tag as a way to organize albums into an arbitrary group. This is useful if a series of media features music that was composed by multiple artists. By matching on the Haibane Renmei grouping, for example, I can find all music that was made for that show, without having to keep artist names in mind.

“Grouping” seemed well-supported in MPD, but whilst updating some albums that I (sadly) only have in MP3 format, I found that MPD would not add the grouping information to its database.

As per the ID3v2.4 standard, the TIT1 frame is used for this kind of information in MP3 files. Sure enough, that tag was set correctly by beets, and both mutagen-inspect and ffprobe found it. MPD, however, even though this PR had been merged almost 3 years ago, refused to pick it up.

After having the #mpd IRC channel sanity-check my configuration, I investigated some more. Perhaps my version of libid3tag was outdated. It wasn’t. Perhaps there were some encoding issues, but then why would other tags from the same file work fine? Couldn’t be that either. I hooked up GDB and found that this line from the PR was never actually reached at all!

I decided to look a bit closer at how exactly MPD reads tags. The specific scan_id3_tag function that the PR modified is only called in two places, plugins/DsdLib.cxx and (indirectly) in plugins/MadDecoderPlugin.cxx. I had neither of these decoders installed, so… MPD just never got to read anything.

Yet how was I getting any tags, then?

After some spelunking in the decoder plugin folders and with the fact on my mind that the only decoder I had actually compiled in was FFmpeg, something dawned on me. Perhaps it was FFmpeg that was reading the tags.

Indeed it was. Turns out that FFmpeg does all of the heavy lifting here, and MPD really just asks it for any metadata and parses the ones it understands.

MPD uses “grouping” as a cross-format identifier for grouping information. It expects that particular string to be a key in the AVDictionary returned by FFmpeg here. Crucially, FFmpeg does not expose TIT1 as “grouping” in its metadata conversion table, having MPD drop TIT1 on the floor like a hot potato.

It is debatable where this particular bug should be fixed. I decided to send a patch upstream to FFmpeg, given that more than just MPD can benefit from a fix there. For the next poor soul I also prepared a PR that clarifies how exactly MPD reads metadata.

My main desktop PC tracks the latest LTS release of the Linux kernel which very recently switched to the 5.15 line. Along with neat new features like the NTFS3 driver it also includes experimental support for Link Time Optimization through LLVM’s clang compiler.

I’m not really one to shy away from weird experiments, so I decided to run a full LTO kernel for a while. If you have a recent version of clang and the lld linker, building one is as easy as toggling CONFIG_LTO_CLANG_FULL and exporting the right flags to make:

make CC=clang LLVM=1 menuconfig
	# CONFIG_LTO_CLANG_FULL=y
make CC=clang LLVM=1

Subsequent steps are the same as with a normal build:

sudo make install
sudo make modules_install

Keep in mind, however, that any out-of-tree modules such as ZFS must also be built with clang. Here I ran into this bug which should soon be fixed upstream. For now I backported that fix locally to ZFS 2.1.2 and am building it like so:

sudo CC=clang LLVM=1 emerge zfs-kmod

Build times and memory usage when building are increased dramatically with full LTO. Optimizing vmlinux.o alone allocates about 3 to 4 GiB of memory. If you rely a lot on incremental builds, thin LTO might be the better option here.

I didn’t expect this to actually work, but as half-jokingly mentioned in the previous post , here’s a Minecraft server running on a machine from around 25 years ago. Pretty much worked out of the box with icedtea assembled by the build VM and a 512MiB swapfile (of which only around 50MiB was actually used).

nortti in the overworld on the syys
Minecraft server
nortti in the overworld on the syys Minecraft server

The game was surprisingly playable, and we made it all the way to finding diamonds.

nortti in the Minecraft on syys
looking at a diamond deposit in the ceiling
We found diamonds!

I spent the last few days building a cursed Gentoo system with my partner. We named it “syys”, after “syyskuu” - the Finnish word for September.

This post was written, committed, and pushed on that system, using only software that was built on it natively. For some heavier parts that were not involved in the making of this post (links, fish, both of which need cmake, which would take hours to build) we set up a portage binary host on a VM that is nearly identical to the actual Pentium II system.

The next logical step, of course, is to try and get Minecraft running on it…

Thunar, XFCE’s file manager, was a pretty late addition to my core set of tools that I rely on to accomplish day-to-day tasks. I started using it heavily maybe 2 or 3 years ago. For the longest time before that I had been using ranger, a console file manager.

The ability to move and copy files around between multiple directories using drag-and-drop is basically Thunar’s killer feature for me. I’m often faster using the mouse to select a bunch of files and then quickly dragging them someplace else. In comparison, ranger’s select-then-yank-and-paste workflow feels very cumbersome.

However, there’s always been a feature in ranger that Thunar did not have - the very simple but powerful ability to filter the current directory listing by showing only files matching a given pattern. There’s a more or less hidden way to have Thunar select files matching a wildcard with CTRL-S, but that relies on popping up an extra dialogue, and doesn’t play well with interactive use.

Very early on I found a feature request for this, but it looked largely abandoned and I forgot about it until today when, to my extreme surprise, I discovered that it was implemented just 3 months ago. There does not seem to have been any large fanfare around it; the changelog buries it in more miscellaneous changes. Not a big deal.

Way more worrisome, however, is that the commit implementing the feature does not introduce any user-facing documentation. Nowhere is explained how the new feature works and what its limitations are. I had to go read the code to find out why my search results were littered with seemingly random files in other directories. Turns out that it consults files in GtkRecent too, merging results in the current directory with matches of files you had recently opened, regardless of their location.

A terrible default in my opinion, so I immediately turned it off by disabling the gtk-recent-files-enabled property in my GTK config. Thankfully you can still do that, albeit in a system-wide fashion, but I don’t care about recent files.

Still, it’s really sad I had to go out of my way to find that out. A less tech-savvy user could not have done that so easily. It would lower the bar tremendously here to describe what a new feature does and point out how to configure it.

A failure to do so makes software intransparent and hostile, furthers the notion that the user experience is inherently bad, and very quickly leads to resignation in the common user base.

A couple of days ago I turned on hidepid=2 for the /proc mount on my desktop PC. Today I saw the systemd-userdbd service failing on bootup, and realized that it had been failing to start for a few days. Aside from the fact that there really should be a well-supported and easy way to notify administrators of failing units, this obviously needed some investigation.

systemctl status systemd-userdbd.service wasn’t very helpful, but journalctl had the specific error:

[..] spawning /lib/systemd/systemd-userdbd: Read-only file system

I didn’t immediately jump to the conclusion that this had to do with enabling hidepid=2. After all, why would that result in a read-only file system? Investigating a failure very early on in boot is a pain, so instead of wasting my time on that I took to GitHub issues.

As it turns out, hidepid= is just not supported at all in systemd. This doesn’t seem to be pointed out anywhere, and I certainly was not aware of it before. Services can set ProtectProc=, but there doesn’t seem to be a clean way of restricting /proc for unprivileged users like a global hidepid=2 did. I’ve removed the option for now.

Yesterday whilst catching up on the Git mailing list I stumbled upon this patch proposing to improve the hunk header regex for Java. I had never paid much attention to how git-diff(1) finds the right method signature to show in the headers though I was vaguely aware of a bunch of regexes for different languages.

Turns out that by default, as explained in the manual for gitattributes(5), git-diff(1) emulates the behaviour of GNU diff -p and does not consult any of the language-specific regular expressions. This came as a bit of a surprise to me, as Git usually has relatively sane and extensive defaults. Why define all these regexes and then not use them by default?

Perhaps one reason is that it is hard to tell when to use which. Git can only look at the filename, and not all shell scripts share the .sh ending, for example. Surely it would not be too invasive, however, to define sensible defaults for, say, files ending in .py or .rs.

In any case I updated my ~/.config/git/attributes with the following, and am now enjoying better hunk headers across the board:

*.c	diff=cpp
*.cpp	diff=cpp
*.go	diff=go
*.md	diff=markdown
*.pl	diff=perl
*.py	diff=python
*.rs	diff=rust
*.sh	diff=bash
*.tex	diff=tex

The markdown setting is especially neat since it will now display the nearest section right in the diff, like so:

--- a/posts/weltschmerz.md
+++ b/posts/weltschmerz.md
@@ -24,6 +24,10 @@ ## Download

I’ve been out and about a lot the past few days, on more or less extensive walks around the countryside. I usually only have my Nexus 6 with me, whose camera, being a 2013 device, is really not the best anymore.

Today I decided to try out my father’s Nikon D5200 instead to get some experience using a more professional device. See below for a small selection of my favourite shots.

Echinops, a globe thistle.
Echinops, a globe thistle.
The fruit of viburnum lantana, the wayfarer.
The fruit of viburnum lantana, the wayfarer.
Acronicta aceris on gravel.
Acronicta aceris on gravel.

In the previous post I talked about a couple of different ways to apply patches with mutt(1) or neomutt(1). Turns out Maildir might not be the best format to use for git-am(1) because its files are not guaranteed to be in any specific order (per spec they need only carry unique names).

As git-am(1) does not sort its input, patches might be applied in the wrong order. This came up on the mailing list as well, all the way back in 2013. A fix specific to Maildir files created by mutt(1) was added in 18505c3.

Sadly neomutt(1) changed this format 5 years ago, removing the sequence number that git-am(1) relies on in commit 75b3708 and replacing it with a call to mutt_rand64(). I can only assume no one is using neomutt(1) to export patches to Maildir, since having patches applied in the wrong order is a pretty significant problem.

For now I recommend using the mbox format instead when exporting patches. Whilst that doesn’t guarantee a specific order either, usually mail clients are nice enough to export mails to mbox in the order they are shown.

The core issue remains until git-am(1) learns to sort mails itself.

When maintaining a project sooner or later there comes the time when you need to apply patches that have been submitted to you. Assuming a patch-based workflow, those are going to be one patch per mail, possibly connected in a thread. There’s lots of different ways of getting those patches to their final destination, git-am(1), and in this post I want to take a look at ones that work well with mutt(1) or neomutt(1) since that is what I use.

I want to rely on the default bindings as much as possible. All mentioned bindings should work out of the box.

Applying a single patch is very straightforward: Select the mail in the pager and hit | to pipe it to an external command. This command will be some variation of git-am(1), perhaps git am -s to also add a Signed-off-by trailer.

What if you want to apply a patch series, however? An obvious solution would be to pipe each message in the thread to git-am(1). The <pipe-message> command we invoked earlier with | only applies to the currently selected message, so we can’t use that on the whole thread. Instead we can tag the whole thread using <Esc>t, then use the <tag-prefix> command ; followed by | to send all tagged messages to git-am(1).

There’s two problems with this, though. The first is that depending on the setting of pipe_split, git-am(1) might only apply the first patch in the series. This is the case if pipe_split is set to the default of no; mutt(1) will then concatenate the messages before sending them to the external command. Sadly this concatenated format is slightly different from the mbox format that git-am(1) expects, making it not see anything past the first patch.

With pipe_split set to yes, mutt(1) spawns one process per tagged mail instead, applying all patches correctly. Now that git-am(1) is spawned once per mail, however, you lose its atomicity: Neither ORIG_HEAD will be set correctly, nor will --abort go back to the original branch.

This might not be a big issue, but I am not a fan. Thankfully git-am(1) supports reading patches from mbox files or Maildir structures. So instead of piping mails to git-am(1) via <pipe-message>, let’s save them to the current directory: With the thread still tagged, ;C (<tag-prefix> followed by <copy-message>) will save a copy of it under a given path. Now you can apply the series by hitting ! and running git am -s <path>. This works with mbox_type set to either mbox or Maildir (but see № 9 ).

Of course there is no need to rely on the default bindings, especially if you need to do this kind of thing very often. mutt(1) is easily customizable, making it possible to bind the entire chain of actions to just one keystroke. If you’re interested in a more detailed examination of a patch-based workflow with mutt(1), check out this post by Greg Kroah-Hartman.

I recently became frightfully aware of how much time I spend in less(1).

It’s never been a utility I gave much conscious thought and I realized that it is one of those programs that go largely ignored and underappreciated just for their ubiquity and unobtrusiveness. For most people piping something to less(1) has most likely become second nature. In the same way it is surely unthinkable for some to read log files and manuals in anything other than less(1), or use any other pager for the Git suite.

If you’ve not given less(1) a closer look, I invite you to read its manual. There’s lots of neat features you might have missed, like filtering lines with &, toggling any command-line option on the fly with -, following input as it appears with F, or opening the current file in an editor with v.

A feature I discovered only recently is the “wrap-around search modifier”. Introduced in late 2020 (past version 565), this modifier obsoletes the common dance of g and n to redo the current search on the whole file. Now, if you hit Ctrl-W right after issuing a forward or backward search, less(1) toggles search wrap-around.

There is no option to turn this behaviour on automatically. However, it is possible to override the / and ? bindings using lesskey(1). Put the following in ~/.lesskey, run lesskey (not needed on versions past 590), and less(1) should now wrap the search automatically:

#command
/ forw-search ^W
? back-search ^W

HTTP defines the Link header with which Link Relations can be defined directly in the HTTP response. This lets websites define their stylesheets or favicons without having to pass them in an HTML <link> element.

When displaying certain files directly, Firefox wraps them in its own HTML. This happens, for example, when you look at a .txt file marked as text/plain or an image marked as image/png.

Turns out that for plain-text files, Firefox will load and honour any stylesheet passed to it in the Link header and apply it to its own HTML. One could, for example, link a stylesheet that overrides the font settings, or even include pictures. Firefox will duly load all of these when displaying the file, whilst View Page Source will still show only the file itself. It is only when looking at the HTTP response and the Network tab in the Inspector that you see what exactly is happening.

Firefox displaying a plain-text file
alongside a picture.
A plain-text file… in Comic Sans… that also displays a picture?

This does not seem to work when Firefox is displaying images and it doesn’t work at all in Chromium-based browsers. The only relevant entry in Mozilla’s Bugzilla instance I could find was this issue from 9 years ago.

Arguably this could be considered a bug, and I do strongly feel that Firefox should not load any outside resource when displaying plain-text files. For now, though, have fun confusing people with this.

Thanks to puck for pointing this out to me.

Even though git-send-email(1) calls git-format-patch(1) if you provide a revision list, oftentimes when submitting a patch series you should instead run git-format-patch(1) first, and later use git-send-email(1) only to submit the files it generated. There’s a bunch of reasons for this.

  1. send-email is built chiefly for mail submission, not patch formatting. There is no way to pass options meant for format-patch, meaning that you miss out on really good features like --cover-letter or --interdiff.

  2. If you use --annotate or decide to edit the mail body, you will lose all changes if you quit before sending. You can’t save your work and continue writing the mail later either. Once you call send-email, you’re committed; It’s all or nothing.

  3. --compose gets you a worse version of format-patch’s --cover-letter. No diffstat is included by default, and the same problems as in 2) apply.

  4. format-patch outputs text files for you to browse and edit. This can be done on your own time and with your own tools, without the send-email prompt nagging and stressing you.

Providing a convenient way of quickly sending out small patches makes sense, but all in all I think the inclusion of formatting in git-send-email(1) is a glaring misfeature. Hiding git-format-patch(1) away from the user makes git-send-email(1) intransparent and, worse, really clunky to use for regular patch workflows.

Sadly, git-send-email.io still tells newcomers to use only get-send-email(1), without even mentioning git-format-patch(1). This won’t change any time soon, either.

That is a shame. Guiding people to a worse workflow will not increase the standing of mail-based processes. For now I’d recommend linking newcomers this section of Git’s own contribution tutorial instead.

Whilst reviewing and discussing this patch for weltschmerz I found out that there actually exists a global property gtk-cursor-blink that is honoured by all applications that build on GTK.

I don’t think I’ve ever seen this exposed in a settings dialog. Perhaps in the accessibility settings, I rarely venture there. In any case, you can put properties like these straight into ~/.config/gtk-3.0/settings.ini and GTK apps will pick them up.

For QT apps it seems you can set cursorFlashTime to a negative value in the aptly named ~/.config/Trolltech.conf but I could not get this to work with the only QT app that I use, quassel.

Not that I would want to, I do prefer a blinking cursor.

The notion of a “merged” branch is highly dependent on the workflow used for a project. I was wanting to clean up some topic branches in my copy of git.git today, but git branch -d refused to delete them, pointing out that they were not yet merged.

I knew for a fact that they were, which made me look up how git branch -d actually determines that. The manual is not entirely clear, but a comment in the code pointed out that git constructs the merge base of the branch and its upstream (or HEAD if there is none) and checks whether the branch is reachable from that merge base.

In a patch workflow, this will generally not be true. A lot of things may happen to your patches before inclusion, and with git.git they will get at least one other sign-off. They’ll be recorded in a merge commit, but it will not have your original branch as one of its parents.

Therefore, neither git branch -d nor git branch --merged will report your branch as merged. Both of these tools are built for the merge workflow instead.

To see if your work was merged in patch-based workflows, use git-cherry(1). Then you can safely force deletion of the branch with git branch -D

In recent versions of Firefox, single words are looked up via DNS when you enter them in the URL bar. If the word resolves, the browser “helpfully” indicates that there’s a page at http://<word> to visit.

Weird feature, though when taken at face value I can see its usefulness with a combined search and URL bar. Ideally you’d have completely different semantics for searching and (direct) browsing, but I don’t think something like this is forthcoming. If anything I’d expect direct browsing to be ever more discouraged. Nowadays search engines, not browsers, are the gateway to the internet.

In any case, turns out this feature also has interesting side-effects with (browser-based) DoH turned on. If you ever need to turn it off, Firefox 78 added the following about:config switch:

browser.urlbar.dnsResolveSingleWordsAfterSearch

Not that I think this is a great solution. DoH should be done on a system-wide level (with local queries sent to your router or resolved through mDNS) and there should be a way of trusting your router’s DNS to not forward local queries to your ISP.

Hi, welcome to my bespoke microblog.

This is where I’ll keep things that aren’t fit for inclusion on my main site, like smaller posts, stuff of less relevance or depth, and anything else for which a proper post would take too much effort.

Expect this space to be updated very frequently. If you dare, subscribe to the feed. Per-tag feeds are also available on the respective tag pages.

Contact

If you want to chat about anything on here, feel free to send me a mail or find me on IRC. The main site has details.

Source

This site is put together with zola. Check out the git repository if you are interested.