Log in
Seblog.nl

Seblog.nl

Zevenheuvelenstress

Zometeen is de start van de Zevenheuvelenloop en hoewel ik weet dat ik er gewoon van moet genieten ben ik behoorlijk zenuwachtig en onzeker. Mijn huidige record is 1:15:10, die liep ik in 2017. Daarna heb ik hem in 2019 nog eens gelopen in 1:22:04. Het idee is dat ik hem deze keer weer rond de 1 uur en 15 minuten zou kunnen lopen.

Maar de zenuwen zitten er dan nu in dat het, als ik dat wil, vandaag wel moet gebeuren. Het is behoorlijk kouder dan de afgelopen tijd, en het gaat waarschijnlijk vanmiddag nog regenen ook. Ik ben nu een beetje aan het samenstellen wat ik aan moet tijdens de wedstrijd, maar waarschijnlijk ga ik net te veel aan hebben omdat ik niet te weinig aan wil hebben.

En dan de training. Het ging echt heel erg goed, met meerdere keren per week een rondje, maar de afgelopen twee weken heb ik het helemaal laten versloffen. Zo voelt het, maar aan de andere kant kan je dit 'taperen' noemen en dan is het precies het juiste. Maar heb ik wel genoeg gegeten in de afgelopen week? Drink ik vandaag genoeg of net te veel? Allemaal onzekerheden.

Een paar weken geleden droomde ik dat ik naar een hardloopwedstrijd had moeten gaan, maar dat ik niet ging, omdat het niet meer ging, omdat ik niets meer gedaan had in de weken ervoor. Ik droomde de zelfsabotage die ik nu een beetje voel dat ik weer aan het doen ben. Je kan ook zeggen dat het druk was op werk.

Enfin, het plan ligt klaar, het horloge aan de oplader, de kleren hier naast me op de bank. Als het echt niet gaat gelden er twee regels: hou je vorm goed, geniet van de tocht.

In mijn post van vorig weekend wilde ik niet toegeven dat de requests die Mastdon naar mijn inbox stuurde helemaal niet door mijn eigen verificatie-code heen kwamen.

Het stappenplan is als volgt: Mastodon stuurt een bericht naar mijn inbox, stuurt daarbij een HTTP-header met daarin een keyId, een signature en een lijstje aan namen van headers. Ik moet dan de waardes van de HTTP-headers bij de namen zoeken, de keyId ophalen (dit is een URL) en dan verifiëren of de signature van de headers inderdaad ondertekend is met de public key uit de keyId. Omdat het cryptografie is gaf de functie alleen maar false terug, zonder enige verder info.

Na heel veel zoeken bleek dat in elk geval de deletes een simpele verklaring hadden: zodra de Actor is verwijderd is ook de public key weg, en dus valt er niets te verifyen. Dat is geen bug, zeggen ze. Ik negeer nu lekker alle Actor-deletes want ik haal toch geen profielen op. Follows en Follow-Undo's die ik als test bleef doen bleven wel falen.

Na letterlijk uren te hebben doorgebracht met specs zoeken en code lezen uit Pixelfed (PHP Laravel) en Pleroma (Elixir), bleef ik er toch echt van overtuigd dat ik alles goed berekende, aan elkaar knoopte, ophaalde, alles. Heel frustrerend.

Maar, wat bleek: de (request-target) vulde ik aan als (request-target) /inbox, terwijl ik mijn inbox op /activitypub/inbox had neergezet. De andere implementaties plakten die path dynamisch op uit de Request/conn. Daar ging 6 uur waarin ik al heel veel verder kon zijn geweest met Activitypub. Maar goed, volgend weekend weer een weekend.

Muis

In de traditie van bloggen over beesten in mijn kamer, door de jaren heen, hier een nieuwe aflevering: ik had een muis.

Ik hoorde mijn buren al eens smoezen dat er muizen waren. O jee, dacht ik, daar heb ik dus hélemaal geen zin in. Ik negeerde het een beetje omdat ik zelf nergens last van had. Mijn bovenbuurman ging flink aan het schrobben, kon ik horen.

Op een dag kwam ik thuis en zag ik het snoer van mijn staafmixer half uit het keukenkastje hangen. Dat is gek, dacht ik nog, dat zou ik toch nooit zo laten hangen, ik werk vast veel te hard. (Ja.)

Een avond later die week was ik dus weer veel te hard aan het werk tot veel te laat, toen ik opeens iets bij de deur van mijn kantoor zag bewegen. Ik schrok daar een beetje van, maar het schrok ook van mij, en ging er in een dolle vaart vandoor.

Dit was het moment dat ik de connectie naar het keukenkastje maakte: in dat kastje zit achterin een gat naar een stopcontact, voor mijn elektrische fornuis. Toen ik het beter inspecteerde zag ik er inderdaad wat zaagsel liggen, alsof er iemand door dat gat naar binnen was gekomen.

Het punt van het keukenkastje is echter: zodra die dicht is gevallen, kan je als muis in elk geval niet zo makkelijk meer naar buiten via diezelfde weg. En verder zijn hier niet zo veel gaten in de muur, wat waarschijnlijk bijdroeg aan het feit dat ik het muizenprobleem tot nu toe kon negeren.

In die week verdween er inderdaad een hagelslagje dat ik zorgvuldig op een vlek in de vloer had gelegd: de muis was er nog steeds. Toch negeerde ik het nog wat verder, tot ik op een zekere nacht wakker werd van gekraak en geknaag.

Na wat gordijnen op zij te hebben getrokken zag ik hem: de muis zat ín mijn slaapkamer. Dit was vorige week zondag. Wat volgde was een halve nacht op de bank (ben ik veel te lang voor), diverse opruimacties om de muis te vinden, slapen op een luchtbed in een andere kamer, en vooral: de deur dicht houden.

Woensdag kwam de DAR langs, en die hebben ons alles uitgelegd over muizen. 'Als er een potlood in kan kan er een muis door.' Ik met grote ogen. 'Als een potlood past, past een muis,' bleef de muizenman stellig. Zo liepen we een rondje rond het huis en vonden we inderdaad veel gaten.

Ik had dingen te doen in de randstad, dus ik heb mijn spullen gepakt en ben een paar dagen bij mijn ouders gaan logeren. Op de kamer stond een val. Nu ik net thuis ben vond ik daar de muis in. Ik moet toegeven dat ik daar dan weer even bijna van moest huilen: het arme beest, moest hij nou echt dood voor mij?

De muis is opgeruimd, de gaten zijn dicht, er ligt gif in de kruipruimte, het lijkt de goede kant op te gaan. Maar zielig is het wel.

Slome Mastodon

Mastodon is nog steeds een hot-topic op mijn Mastodon- en feedreader-tijdlijn. Zo kwam ik dit artikel van Aral tegen waarin hij uitlegt hoe een beetje populair zijn (22.000 volgers op 3000 servers) en een beetje actief posten en reacties uitlokken al snel kan leiden tot een enorme queue en dus vastzittende instances.

Zijn oplossing: populaire mensen moeten hun eigen server draaien. Ergens is daar wat voor te zeggen, op Twitter waren het ook de populaire mensen die het meeste geld kosten (en opleverden waarschijnlijk maar soit). Het is logisch dat als jij veel interactie hebt, computers daar dus harder voor moeten werken, en dat je dan misschien wat meer moet betalen aan je Mastodon-host.

Maar los van dat computers draaien geld kost, kosten computers ook stroom en dus naar alle waarschijnlijkheid CO². Een beetje minder daarvan zou best fijn zijn, en dit is in mijn optiek wel een beetje een tekortkoming van Activitypub (te actief) en Mastodon (gekke technische keuzes).

Toen Twitter begon bouwden ze het platform op Ruby on Rails, een framework bekend om het gemak waarmee je een nieuwe applicatie kan opzetten, maar niet om z'n snelheid. Twitter liep echter tegen de grenzen aan van die architectuur, met veel plaatjes van een door vogels opgetilde walvis als het weer mis ging. In stapten ze over naar een andere architectuur en kwam er eindelijk een eind aan de 'fail whale'.

In 2016 deed Gargron de eerste commit voor Mastodon. Het zette een lege Ruby on Rails applicatie neer, die later uitgroeide tot de Mastodon die we nu hebben. Als het Twitter niet lukte ermee te schalen, heb ik ook vraagtekens of Mastodon het wel kan.

Maar daarnaast ook Activitypub nogal onnodig werk: op moment van schrijven heb ik drie features van het protocol geïmplementeerd op dit weblog (webfinger, de actor, de inbox) en één keer mijn 'profiel' bezocht via Mastodon.social. Sindsdien heb ik 1507 berichten ontvangen over verwijderde gebruikers. 1507 berichten, alleen omdat iemand anders (oké ik zelf) mijn profiel bezocht. (Ik begon vanochtend al met schrijven aan deze post, maar inmiddels zie ik dat Aaron nog meer deletes heeft ontvangen. Oeps.)

Ik ben een voorstander van 'eerst gebruiken dan pas de protocollen', en aangezien we nu Activitypub gebruiken is dat een beter protocol dan elke nieuwe die we verzinnen zonder actieve gebruikers, maar ergens hoop ik wel dat we dit nog kunnen fixen.

Poging tot Mastodon

In navolging van velen sinds het aantreden van de nieuwe Oppervogelman ben ik vandaag bezig geweest met 'overstappen' naar Mastodon. In mijn geval betekende dat het opsnorren van mijn oude account, en een zoektocht naar de vraag: hoe werkt dat eigenlijk, Activitypub?

Als achtergrond: Mastodon's mogelijkheid om over meerdere servers heen met elkaar te kunnen praten is niet beperkt tot alleen servers die Mastodon-software draaien. Het is gebouwd op een open standaard die Activitypub heet. Ik heb vandaag een poging gedaan om die een beetje beter te begrijpen.

De volledige uitleg ga ik hier niet geven, want ik ben er ook maar pas net ingedoken. Maar interessante artikelen zijn deze van Eugen Rochko, de maker van Mastodon, en ook de followup ervan. Eugen legt hierin uit hoe je met wat statische bestanden en een beetje Ruby een paar Activitypub-dingen kan doen.

Een van die dingen heb ik toegepast: je kan dit domein nu webfingeren (jaja) en daarmee kan je mijn Activitypub Actor vinden, die weer leidt tot een Activitypub Inbox. Op het moment van schrijven is het er allemaal wel, maar ik beloof niets voor de toekomst, want ik ben er nog niet helemaal over uit of ik dit echt wil.

Het gevolg van het bovenstaande is dat je nu op @seb@seblog.nl kan zoeken in je favoriete Mastodon-instance, en dat je mij daar dan ziet verschijnen. Je kan daar naar alle waarschijnlijkheid ook op 'Follow' drukken en daar krijg ik dan een melding over. Vervolgens doe ik daar helemaal niets mee, want zo ver ben ik gewoon nog niet.

Ik heb veel horrorverhalen gehoord over Activitypub en ik moet zeggen dat ze deels waar zijn: het is inderdaad behoorlijk veel gedoe allemaal. Om een bericht te accepteren moet je dingen doen met public en private keys, waarvan ik nog steeds denk dat ik het niet helemaal goed doe.

Bovendien ontvang ik sinds ik mijn naam bij Mastodon.social heb ingevoerd allerhande berichten over verwijderde gebruikers. Ik heb nooit om een lijst van alle gebruikers gevraagd, dus ik ben ook niet per se geïnteresseerd in elke verwijderde gebruiker, maar ik ontvang er nu wel bericht over. Ik hoop niet dat elke server in de Fediverse dit gaat doen, want dan ben ik er wel snel klaar mee.

Misschien dat ik morgen nog wat dieper duik, maar zoals gezegd beloof ik niets en verwijder ik de functionaliteit misschien zelfs wel. Het idee is alleen zo vet: met zo veel meer mensen in contact staan via een dergelijk protocol.

Wat verder ook niet onvermeld moet blijven is deze geweldige post over hoe je de verschillende Activitypub specs zou moeten lezen, in welke volgorde en hoe de boel zich tot elkaar verhoudt. Maar dat dus misschien voor morgen. Tot toots.

Ik heb uitgevonden dat ik nog een account op Mastodon had liggen uit 2016. Op dit moment accepteert die instance (Mastodon.social) geen nieuwe gebruikers meer, mede om decentralisatie te promoten, maar misschien ook om de load op de server beheersbaar te houden.

Die instance heeft dan wel een interessant extra 'probleem': als er veel mensen al een account hadden kunnen ze die allemaal activeren, om zo de server het alsnog zwaar te laten hebben. Een Mastodon zombie apocalyps.

Ik heb het een tijdje volgehouden, maar vandaag is het zo ver: ik heb de lader van mijn Apple Watch niet bij me, maar hij is wel leeg. Sinds 16 september heb ik de Series 8 en die draag ik nu ook 's nachts, maar dat betekent dat ik ergens op de dag moet bijladen. Wel 7 hele weken goed gegaan, maar vandaag dus niet.

Terug naar de reader

De afgelopen jaren ben ik steeds minder actief op Twitter. Ik tel ongeveer 13 tweets dit jaar, 17 tussen nu en 1 november 2021. Ik heb altijd het idee dat ik in een leeg gat praat: de mensen die mij volgen zeggen niets terug en iedereen die ik zelf volg lijkt mij niet te volgen. Ik heb 480 volgers, maar mijn recente tweet kwam niet boven de 50 weergaven.

Nu met de ontwikkelingen rond de nieuwe Oppervogelman vraag ik me af: moet ik wel welke dag zijn app openen en doorscrollen? Dus nu ben ik weer voor de zoveelste keer terug bij een RSS-reader, ditmaal een Miniflux, gehost en mij aangeboden door Henrique.

Op moment van schrijven zit ik op 10 feeds, na dit weekend met 5 te zijn begonnen. Ik heb namelijk wel vaker feedreaders gebruikt, en altijd val ik in dezelfde val:

  • ik gebruik geen feedreader
  • ik neem er een, volg een paar mensen
  • nog steeds erg leeg
  • ik volg iedereen die ik maar kan verzinnen
  • aaaaaaa
  • ik gebruik geen feedreader

Hoewel ik steeds weer tegen Twitter zeg dat ik geen algoritmische tijdlijn wil, wil ik een algoritmische tijdlijn, blijkt keer op keer opnieuw.

Het grote voordeel van een feedreader is echter dat het me indirect aanmoedigt om meer te bloggen. Dit is waarschijnlijk vreemd: de meeste mensen gaan op Twitter omdat je daar makkelijker kan antwoorden. Omdat ik mezelf de regel heb gesteld dat alle antwoorden ook op dit blog moeten verschijnen heb ik reageren op Twitter zó moeilijk gemaakt dat ik het nooit doe.

Enfin, we zien wel waar deze episode eindigt. De vorige is alweer van 2020, zo blijkt. Wie erover schrijft kan terugvinden.

Ton schrijft over verhaspelde namen omdat Wouter erover begon.

Zelf heb ik een naam waar veel Nederlanders graag een 'r' in plaatsen: Anderweg. Maar de mooiste was op de ov-chipkaart van mijn broertje: daar had de NS er 'Onderweg' van gemaakt, wat ook wel erg toepasselijk was.

Using PHPStan to fill Vim's Quickfix list

I am using PHPStan and the ALE plugin to add some error checking to my Vim. It gives red arrows on lines that contain errors in my currently opened files. But sometimes, in a big refactor, I want to know all errors in my project.

Vim has a build-in feature for this: the quickfix list. It is designed to take the error output of a compiler and lets you jump to all those locations with the :cnext and :cprev commands. I personally use the essential Unimpaired plugin by the one and only tpope, which maps these to ]q and [q.

I use these a lot: the :grep command fills the quickfix list with all the occurrences of your search, like normal grep but with pagination (or, if you set your grepprg to something faster: like ripgrep with pagination).

To get PHPStan to fill this quickfix list, I looked for plugins, but they all seemed hairy. I was convinced this should be simple, and it was. The following leader mapping seems to just work:

nmap <leader>pa :cexpr system('vendor/bin/phpstan analyse --no-progress --error-format=raw')<cr>

Customising Git: some things I did

One thing that always puzzled me a bit about my own workflow, is that almost all of it is based in the terminal (I use Vim and Tmux), except for Git: where most people seem to use the Git CLI commands, I use a graphical program (Fork, which is quite good).

Another thing then: I never used Github professionally, apart from the time I was a self employed web developer, but back then I was the only developer on my projects. All my previous jobs had a self-hosted Gitlab running somewhere.

Long story short: I am trying to get better at Git in the terminal and using Github. And 'better at Git' to me both means 'being able to confidently rebase' as well as 'customise my workflow'.

Aliases in the Gitconfig file

Customising Git means setting configuration in the ~/.gitconfig file. This file contains settings for Git, like your name and email, but can also be used to add aliases. To create the first alias you can run git config --global alias.co checkout. After this, you can use git co as git checkout, which is shorter to type and yes I use this often now.

Another alias I have is this one:

publish = !git push --set-upstream origin $(git symbolic-ref --short HEAD)

If you try to push a branch that has no linked branch on Github (the upstream), Git will complain about it. It will be nice to you and state the command you should have ran, but I got tired of having to copy and paste that new command.

fatal: The current branch feature/new-shoes has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin feature/new-shoes

In Fork, this was just a checkbox away. With my new git publish command I get the convenience again: it will push the branch and set the upstream with the same name as I have locally, exactly as Git suggested I should have done, but in less typing.

Links to Github

So far we have seen two kinds of aliases: one that just aliases a simple Git command (b = branch) and one that actually ran a shell command, because we started it with a ! (note that you will have to start with ! git there). But there is another way: having a command that starts with git- in your path.

I have the following file as ~/bin/git-github, marked as executable (chmod +x ~/bin/git-github) and in my path (export PATH="$HOME/bin:$PATH" in my ~/.zshrc file):

#!/bin/zsh

local url
url=$(git remote get-url $(git remote))
url=$(echo $url | sed 's/.*github\.com[:\/]\(.*\).git$/https:\/\/github.com\/\1/')

if [ -n "$1" ]; then
  append="/$1/$(git symbolic-ref --short HEAD)"
fi

open -u "$url$append"

Yes, my ZSH is crude. Yes, I can better share this in Bash. Yes, it could've probably been on one line and be included as an alias in my Gitconfig. But it works for me.

The command figures out the Github URL of the project, based on the URL of the remote (it assumes you have only one). It then opens that URL with the macOS open command. If an argument is given, it appends that to the URL, with the name of the branch too.

In my Gitconfig I have two aliases that use this command:

pr = github pull/new
compare = github compare

With git github, my default browser will open a tab with the 'homepage' of the repository. With git compare, it will open a tab that contains a diff of the current branch and the default branch on Github (the remote versions of those). With git pr, the browser will open the correct page to open a for the current branch.

And you mentioned Vim and Tmux?

Yes, I actually use the obligatory Tim Pope plugin Fugitive. This means I can do a lot of things which you can also read about in the help file. (Which I read a lot these days.)

But this allows me to stage and commit and rebase and reword all my changes in Vim, and then when I am ready, run :G publish and :G pr and make a PR for it on Github. These two commands alone make me feel so much more productive: no longer do I have to search for another program to compose commits and then search for the browser to handle the cooperative side of it... I just handle everything in my editor.

For Tmux I have another nice addition: in my ~/.tmux.conf I have the following command:

bind-key y display-popup -E -h "90%" "git log --oneline --decorate --graph --all"

This will – when I press <prefix> + y – open a popup window, which closes when the command exits and has a certain height. It will show me an ASCII-art style graph of all the commits – one of the features I missed from Fork – as an overlay over my editor, a small keystroke away.

With this configuration – and a lot of reading the Fugitive help file – I feel much more at home with Git in the terminal.

I also watched this fantastic series on mastering Git and these unfortunately dated but still useful screencasts on Fugitive.

Storing posts by juggling with Git internals

I have been wanting to rework the core of this website for a couple of years now, but since the current setup still works, and since I have many other things to do, and finally since I am very picky about how I want it to work, I have never really finished this part at all. This makes me stuck at the save version of this site, both visually as behind the scenes.

Now that I am in between jobs I wanted to work on it a bit more, but I still do not have time enough to fully finish it. I guess it all comes down to a few choices I have to make regarding trade-offs. In order to make better decisions, I wanted document my current storage and the one I have been working on. After I wrote it all out I think I am deciding not to use it, but it was a nice exploration so I will share it anyway.

The description heavily leans on some knowledge about Git, which is software for versioning your code, or in this case, plain text files. I will try to explain a bit along the way but it is useful to have some familiarity with it already.

tl;dr: I did fancy with Git but might not pursue.

How it is currently done

At the time of writing, my posts are stored in a plain text format with a lot of folders. It is derived from the format which the Kirby CMS expects: folders for pages with text files within it, of which the name of the text file dictates the template that is being used to render the page. In my case, it is always entry.txt.

I have one folder per year, one folder per day of the year and one folder per post of the day. In that last folder is the entry.txt and some other files related to the post, like pictures, but also metadata like received and sent Webmentions.

An example of the tree view is below. It shows two entries on two days in one year. Note that days and years also have their own .txt file that is actually almost empty and pretty much useless in this setup, but still required for Kirby to work properly. The first day of the year my site is broken because it does not automatically create the required year.txt (or did I fix that finally?).

./content
└── 2022
    ├── 001
    │   ├── 1
    │   │   └── entry.txt
    │   │   ├── .webmentions
    │   │   │   ├── 1641117941-f6bc3209f3f33f0cb8e4d92e5d46b5090b53aa11.json
    │   │   │   └── pings.json
    │   └── day.txt
    ├── 002
    │   ├── 1
    │   │   ├── some_image.jpg
    │   │   └── entry.txt
    │   └── day.txt
    └── year.txt

Also note that there is a hidden .webmentions folder which contains a pings.json for all the sent webmentions and a JSON file with timestamp and content hash in the name for every received webmention. Not in the diagram but also present are some other folders for pages like ./login/login.txt (because that is how Kirby works) and ./isbn/9780349411903/book.txt (for books).

All these files are stored in a Git repository, which I manually update every so often (more bimonthly than weekly, sadly) via SSH to my server. I give it a very generic commit name (’sync’ or so) and push to a private repo on Github, which takes a while because the commits and the repo contain all those images and all those folders.

What is wrong with this

The main point of wanting to move off of this structure by Kirby, is that it requires those placeholder pages in my content folder. I have no need for a ./login/login.txt: the login page is just a feature of the software and should be handled by that part of the code. But at least that file contains some text for that page: the files for year.txt and day.txt are completely useless.

Another point is that I want to make the Git commits automatically with every Micropub request: Git provides me with a history, but only if I actually commit the files once I changed them. Also, if I do not push the changes to Github, I have no backup of recent posts.

The metadata of the received and sent Webmentions are now also available in the repo. This is nice, as it stores the information right next to the post it belongs to, but on the other hand it feels kind of polluting: these Webmentions contain content by others, where as the rest of the content is by me. There is some other external content hidden in the entry.txt file but I’ll get to that later.

The last point is that the full size images are stored in the repo and every book and article about Git says that you should not use it to store big files in it. Doing a git status takes a while and also the pushes are much slower than any other Git repository I work with.

Git history: the Git Object Model

Before I go further into the avenue I am taking to solve the problem, I need to explain a bit about the Git Object Model, also known as ‘how Git works under the hood’. For a more thorough explanation, see this chapter in the Git Book.

As you’ll learn from that chapter, every object is represented as a file, referenced by the SHA1 hash of its contents. And there are three (no, four) types of objects:

  • blobs, which are the contents of files tracked by Git (and thus also the versions of those files)
  • trees, which are listings of filenames with references to blobs or other trees. These trees together create the file structure of a version.
  • commits, which are versions. A commit contains a reference to the root tree of the files you are tracking, a parent commit (the previous version) and a message and some metadata.
  • tags, are not mentioned by the chapter, but do exists: these look like commits, but create a way to store a message with a tag (making annotated tags, I’ll explain plain tags soon).

Note that Git does not store diffs, it always stores the full contents of every version of the file, albeit zlib compressed and sometimes even packed in a single file, but let’s not get into that right now.

Git’s tags and branches are just files and folders (they can have / in their names) which contain the hashes (names) of the specific commits they point to. The tags can also point to a tag object, which will then contain a message about the tag (which makes them ‘annotated tags’).

This all brings me to the final point about my storage: for every new post, Git has to create a lot of files. First, it needs to add a blob for the entry.txt, possibly also a blob for the image and blobs for other metadata. Then it needs to create a tree for the entry folder, listing entry.txt and if present the filenames of the images and metadata files. Then it creates a new tree for the day, with all the existing entries plus the newly created one. Then it creates a new tree for the year, to point to this new version (tree) of the day. Then it creates a new tree for the root, with this new version of the year in it. And finally it also needs to create a commit object to point to that new root tree. Every update requires all these new trees. The trees are cheap, but it feels wasteful.

Also note that a version of a file always relies on the version of all other files. This is what you want for code (code is designed to work with other code), but it does not feel like the right model for posts (I might come back on this tho).

And there is also the question of identifiers: currently, my posts are identified as year, day of year, number (2022/242/1), but especially that number can only be found in the name of the folder and thus in the tree, not in the blob. I have not yet found a good solution for this, but maybe I am seeing too many problems.

The new setup

To get rid of some of the trees, I tried to apply my knowledge of the Git Object Model to store my posts in another way. To do this, I used the commands suggested by the chapter in the Git Book in a script that looped over all my files, to store them in a new blank repo to try things out.

For each year, for each day, for each post, I would find the entry.txt and put the contents in a Git blob with git hash-object -w ./content/2022/242/1/entry.txt. The resulting hash I used in the command git update-index --add --cacheinfo 100644 $hash entry.txt to stage the file for a new tree. I would do that too for all images and related files, and then I would run git write-tree to write the tree and get the hash for it and git commit-tree $hash -m "commit" to create a commit based on it (with a bad message indeed). With that last hash I would run git update-ref refs/heads/2022/242/1 $hash to create a branch for that commit. (I contemplate adding an annotated tag in between, for storing some metadata like ‘published at’ date.)

This would result in a Git repository with over 10,000 branches (I have many posts) neatly organised in folders per year and day. When one were to check out one of these branches, just the files of that posts will appear in the root of your repo: there are no folders. When you check out another branch, other files will appear. This is not how Git usually works, but it decouples all posts from one-another.

Multiple types of pages

The posts I describe above all follow the year-day-number pattern because they are posts: they are sequential entries tied to a date. There are other objects I track, though, that are not date-specific. One example is topical wiki-style pages: these pages may receive edits over time, but their topic is not tied to a date. (I don’t have these yet.)

Another example is the books that I track to base my ‘read’ posts off. I haven’t posted them in a while, but I would like to expand this book collection to also include other types of objects to reference, such as movies, games or locations. These objects also have no date to them attached, at least not a date meaningful to my posts.

I could generate UUIDs for these objects and pages, and store branches for those commits in the same way Git does store it’s objects internally, with a folder per first two characters of the hash (or UUID) and a filename of the rest:

./refs/heads
├── 0a
│   ├── 8342d2-d6f1-4363-a287-a32948d04eaa
│   └── edcb13-433c-48d2-b683-a407c3a88f57
└── 3d
    ├── 243a27-114e-4eee-9bd8-2a51b01939e6
    ├── 25965b-2da5-422d-abce-f3337fa97fc4
    └── 611b59-499a-48a0-b931-afe06192e778

I could even reference the same post/object with multiple identifiers this way. Maybe I want to give every book a UUID, but also reference it by its ISBN. The downside to that, however, is that I need to update both branches to point to the same commit once I make an update do the book-page.

Drawbacks of the approach

The multiple identifiers are probably not feasible, but there are some other drawbacks too. My main concern is that it is much harder to know whether or not you pushed all the changes: one would have to loop over all 10,000+ branches and perform a push or check. In this loop you would probably have to check out the branch as well. It is of course better to just push right after you make a change, but my point is that the ‘just for sure’ push is a lot of work.

Another drawback is actually the counter to what I initially was seeking: wiki-style pages might actually reference each other, and thus their version may depend on a version of another page. In this case, you would want the history to capture all the pages, just as the normal Git workings do. My problem was with the date-specific posts, but once you are mixing date-specific and wiki-style pages, you might be better off with the all-file history.

One problem this whole setup still does not solve is that of large files. The git status command is much faster for it does not have to check all the blobs in the repo to get an answer, but the files are still in the repo, taking up space. And there do exist other solutions for big files in git, such as Git LFS, the Large File Storage extension.

Also, I am still not 100% sure it is a good idea to store metadata in the Git commits and tags. When we already store the identifier in the tree objects, I thought I could also add the ‘published at’ date into the commit. Information about the author is already present, and as my site supports private posts, it also seemed like a reasonable location to store lists of people who can view the post. But again, maybe that should be stored in another way, and not be so deeply integrated with Git.

Conclusion

It was very helpful to write this all out, for by doing so I made up my mind: this is just all a bit too complicated and way too much deeply coupled to Git internals. I would be throwing out the ‘just plain text files’ principle, because I would store a lot of data in Git’s objects, which are actually not plain text, since they are compressed with a certain algorithm.

My favourite Git GUI Fork is able to work with the monstrous repository my script produced, but many of the features are now strange and unusable, because the repo is so strangely set up. I would have to create my own software to maintain the integrity of the repo and that could lead to bugs and thus faulty data and maybe even data loss.

I still think there are some nice properties to the system I describe above, but I won’t be using it. But I learned a few new things about Git internals along the way, and I hope you did too.

Quickly look up PHP docs from Vim

As I don’t use a full IDE like PHPStorm, I don’t get much help with the parameters to function calls from my editor. On one hand I find this a good thing: IDE users rely so much on autocomplete that they don’t remember names of things at all. On the other hand: who has the mental space to waist on such things?

My middle ground is that I look up a lot of things on PHP.net. They make that very simple: just add the name of the function you are looking for after the slash and they will give you the correct documentation. But: switching to a browser and typing out the address requires a lot of keystrokes. I found a solution.

nmap <Leader>pd :silent !open https://php.net/<c-r><c-w><cr>

This adds a leader key mapping to look up the word (so: function name) under the cursor. I prefix all my PHP related leader mappings with a p, but feel free to pick something else.

The :! runs a shell command, in this case open, which on Mac can be given a URL, which will then be opened in the default browser. I added silent to ignore the output of the command: I just want to open a URL.

As the URL, I let Vim type out the address to PHP.net, and since we are in command mode, one can do Ctrl+R, Ctrl+W, which will paste the word that is currently under the cursor (very nice to know in itself). We end the sequence with an enter (carriage return) to run it.

So the tip within the tip is Ctrl+R (register) Ctrl+W (word). In general Ctrl+R in insert mode gives you this interesting ‘paste from register’ mode that is good to know. See :help i_ctrl-r and :help c_ctrl-r for more.

Ik weet weer het antwoord op de vraag ‘waarom heb ik geen secondelijm in huis’, wat een nare vingertoppen krijg je ervan. Maar het is per saldo vast beter voor het milieu dan een nieuw telefoonhoesje kopen.

Meer laden