Benjamin Sago / ogham / cairnrefinery / etc…

Technical notes Pin casks installed by Homebrew Cask

Homebrew Cask is like Homebrew, but for applications, rather than command-line binaries. I use it to install applications and fonts.

A feature that Homebrew used to offer but no longer does is being able to “pin” a version of an installed cask, so that it won’t update when you run brew --cask upgrade. It’s a shame this feature was removed, as whenever I ran that command, I used to have to clean up the list of upgradable casks manually. The reasons I hold back upgrades are:

  1. An application is upgrading to a new version that requires a new paid licence, and I’m happy with the existing one’s set of features; or, it’s upgrading to a new version I’m no longer fond of.

  2. An application is upgrading to a new version that’s no longer compatible with Catalina, which I’m still running.

  3. The cask has its “version” field set to latest — lots of font casks are like this — meaning that Homebrew has to upgrade, as it cannot tell whether it’s been updated since the last upgrade.

In one of the GitHub issues requesting pinning, the developers give a reason for not supporting this feature: because some applications handle their own updates themselves, there’s no legitimate way that Homebrew can pin a version, ensuring that it stays at this version until explicitly unpinned. This is true, and is one of the downsides of having Homebrew be supplementary to the traditional practice of downloading .dmg files from the Internet (in contrast to Linux package managers, which are usually the user’s primary way of installing software).

However, I’m not looking for a hermetically-reproducible development enviroment, the sort of thing that Nix would give you — I’m simply looking for those casks to not be offered for auto-update, as I can disable auto-updates in the applications themselves, and I don’t really need to install the latest fonts to survive in this world.

Sometimes you’ll find old versions hanging about in homebrew-cask-versions after retirement, but this isn’t the case for every cask, and even if you find an older version in that repository, it’s sometimes a pain to uninstall one version of an application and reinstall another.

Holding back versions

One way to get around this is to use a third-party Homebrew Cask manager, brew-cask-upgrade. You can then run brew cu to upgrade after running brew cu pin <some-cask> to pin the packages you need.

I wrote this script instead though:

#!/usr/bin/env fish
set -l casks (brew outdated --cask --greedy --verbose \
               | egrep -v '^(font-crimson-pro|keyboard-maestro|vmware-fusion) ' \
               | sort -r)

if test -z "$casks"; then
    echo "cask-upgrade: no casks available" >/dev/stderr
    exit 2

set -l choices (printf '%s\n' $casks | fzf --multi --height=(count $casks))
if test -z "$choices"
    echo "cask-upgrade: nothing selected to upgrade" >/dev/stderr

set choices (printf '%s\n' $choices | awk '{print $1}')
exec brew upgrade --cask --greedy $choices

The script first runs brew outdated --cask to get the list of all available Cask upgrades, using the --greedy flag to include auto-updating casks, and the --verbose flag to also list the version being upgraded to. It filters the list of casks through egrep -v, which removes all the entries that match the given regex. Here, I’m removing three casks from the list. It then passes the filtered casks list to fzf --multi, meaning I can select exactly which casks I want to upgrade from a TUI selection interface. Finally, the list is stripped of its version information (by only printing the first “field” in each line), and passed to brew upgrade --cask.

Inside the fzf, I can hit Tab to select the packages I want, Up and Down to move, and Return to confirm.

This lets me easily see what’s being upgraded each time, and decide whether or not to update it or leave the update for another day, while not making me think about all the casks I want to leave behind permanently.