Abdulkader Safi

Who changed this page?

GitBasedDocs pages now show who changed them last, pulled from the GitHub commit list at sync time. The commit id and GitHub link shipped for editors too, then moved to admins only.

4 min read

Share
GitBasedDocs page header showing Updated 3 hours ago by Abdulkader Safi with a commit id and View on GitHub link

Every page in GitBasedDocs now has a line under its title like this one:

Updated 3 hours ago by Abdulkader Safi · f5e132e · View on GitHub

The first half shows for everyone. The commit id and the GitHub link show for admins only. Getting there took two commits on September 10, and the second one took something away from the first.

Where the date came from before

Until then, "Updated" was the time the sync stored the page's latest version. That is the app's clock, not the writer's. The first sync of a repo stamps every page with the same moment, even pages nobody has touched in months. Without a webhook, an edit can wait for the timed recheck and show up minutes late. Git already knows who touched the file and when, so the page should say that.

One extra request per changed page

A sync already walks the repo tree, and the tree has no commit information in it. Each entry is a path, a type, a blob sha and a size. To learn who changed a file, you ask GitHub's commit list for that path and take the newest one:

`/repos/${owner}/${repo}/commits?sha=${encodeURIComponent(ref)}&path=${encodeURIComponent(path)}&per_page=1`

lastCommit() in lib/github/commits.ts makes that call for each page the sync actually fetches. Delta sync only fetches pages whose blob sha changed, so a push that edits two files costs two extra requests, not one per page in the repo. The ref is the head sha the sync is working from, so the commit matches the content being stored.

The call is optional on purpose. Any failure, a rate limit included, returns null and the page syncs without a commit. A page with no author is a smaller problem than a sync that stops because GitHub said no to a nice-to-have.

commitFrom() reduces GitHub's response to the four things a page shows: the sha, the author name, the date and the first line of the message. The author name falls back to the GitHub login, then to "unknown". An empty list, an error object or an unparseable date all return null. That function has its own check, commits.check.ts, built from a real response for one of my vault's backup commits.

Four columns and a forced refetch

The result is stored on the page row. Migration 0009_page_last_commit.sql adds four columns to doc_pages, then does something less obvious:

-- Existing pages have no commit yet. Forget their blob shas so the next sync
-- fetches each once more and records who changed it last.
UPDATE `doc_pages` SET `blob_sha` = NULL;
UPDATE `repo_connections` SET `last_synced_sha` = NULL;

Delta sync skips any page whose blob sha has not changed, which would leave every existing page without a commit until someone edited it. Clearing the stored shas makes the next sync treat every page as changed and fetch each one once. After it ran, 11 of 11 pages had a commit.

On the page, the time comes from the commit, with the sync time as a fallback for a page that has none:

Updated {relativeTime(page.lastCommitAt ?? page.updatedAt)}
{page.lastCommitAuthor && <> by {page.lastCommitAuthor}</>}

Hovering the time shows the full date, and hovering the commit id shows the first line of the commit message.

Then I took half of it back

The first version (3346bd1) split it by role. Admins and editors got the commit id as a link to GitHub, plus a "View on GitHub" link to the file. Viewers got the short sha as plain text, since the repo is private and a link would only 404 for them.

Reviewing it, I wanted less. A viewer is usually a client, and the commit id is a pointer into a repo they cannot open. The repo is how the docs get published, not part of the docs, so I wanted repo details in front of admins only.

So 8436b3f changed the rule to admins only:

// Repo details (the commit and the link to the file) are for admins only.
// Everyone else sees who changed the page and when, nothing that points
// into the private repo.
const connection = isAdmin ? await getConnection() : null

Without the connection there is no owner or repo name to build a URL from, so both links disappear together and the plain-text sha went with them. Editors and viewers see "Updated 3 hours ago by Abdulkader Safi" and nothing that links into the repo. I checked it over HTTP signed in as each of the three roles.

Last updated 10 Sep 2026 · filed under Typescript, web application, nextjs

Every entry on this project

19 build notes, in order

Including the ones where nothing worked. You are on part 8.

Read the series →

FAQ

Frequently asked questions

How does GitBasedDocs know who last changed a page?

During a sync it asks GitHub's commit list for the newest commit that touched the file, at the head sha being synced. It only does this for pages whose blob sha changed, so a push that edits two files costs two extra requests. If the call fails, the page syncs without a commit.

Why can only admins see the commit id and the GitHub link?

Both point into the private content repo. Viewers are usually clients who cannot open it, and the repo is how docs get published rather than part of the docs. Editors and viewers see who changed the page and when, and nothing that links into the repo.

Written by

Abdulkader Safi

Software Engineer

Lead engineer at DSRPT, from Lebanon and based in Kuwait. I write about the tools and bugs from real client work, with the numbers I measured.

About me → GitHub LinkedIn

Need this kind of work done on your project?

Start a project →

Keep reading

More from GitBasedDocs

All 19 entries →
  • The GitBasedDocs search palette showing results for a partial word
    GitBasedDocs

    · part 19 of 19

    Search that quotes whatever you type

    GitBasedDocs search moved from LIKE over every page body to SQLite FTS5 kept in step by triggers. Word prefixes and accent folding came with the tokenizer. The real work was quoting input, choosing a stable order, and a backup detail about rowids.

  • A GitBasedDocs doc page with a Linked from list under the article
    GitBasedDocs

    · part 18 of 19

    Backlinks that use the renderer's own link rules

    GitBasedDocs pages now show which pages link to them, resolve Obsidian aliases, open embedded PDFs and list recent changes. The backlinks reuse the functions that turn links into hrefs, so a page appears under Linked from exactly when its link works.

  • A docker-compose.yml with BACKUP_ENABLED set to true and a folder of dated backup archives
    GitBasedDocs

    · part 17 of 19

    Backups behind one flag in docker compose

    GitBasedDocs now has a Dockerfile, a compose file and backups you turn on with BACKUP_ENABLED. Each archive is a VACUUM INTO copy plus the asset files, and the copy is edited before it is packed so a restored app catches up on its own.