Git-backed document versioning with Memento access to the history

A dataspace can be configured to mirror every document write into a GitHub repository. The synchronization goes through the GitHub REST API (api.github.com), so it is specific to GitHub rather than generic Git. Every write becomes a commit authored with the writing agent's WebID, so the repository holds a complete, attributable history of the dataspace's documents.

The history is accessible over HTTP through the standard Memento (RFC 7089) protocol — historical versions, a TimeMap, and datetime negotiation through a TimeGate — as well as through the version history dialog in the user interface and the command line interface.

Setup

Versioning is enabled per dataspace:

  1. Create a GitHub repository to hold the version history, and a fine-grained personal access token with read/write access to that repository's contents
  2. In config/system.trig, point the dataspace at a repository description with lds:versioningRepository:
    <urn:linkeddatahub:apps/end-user>
    {
        <urn:linkeddatahub:apps/end-user> a lds:EndUserDataspace ;
            lds:service <urn:linkeddatahub:services/end-user> ;
            lds:versioningRepository <urn:linkeddatahub:versioning/end-user> .
    }
    
    <urn:linkeddatahub:versioning/end-user>
    {
        <urn:linkeddatahub:versioning/end-user> a doap:GitRepository ;
            doap:location <https://github.com/OWNER/REPO> ;
            github:branch "main" ;
            github:pathPrefix "graphs" .
    }
    The github: terms come from the https://w3id.org/atomgraph/linkeddatahub/services/github# namespace; github:branch defaults to main and github:pathPrefix to graphs.
  3. Add the access token to secrets/credentials.trig (and make sure the credentials secret is enabled in docker-compose.yml):
    @prefix a: <https://w3id.org/atomgraph/core#> .
    
    <urn:linkeddatahub:versioning/end-user>
    {
        <urn:linkeddatahub:versioning/end-user> a:authToken "github_pat_..." .
    }
  4. Restart LinkedDataHub

From then on, every successful document write in the dataspace is committed to the repository. Writes from different documents share one commit chain per branch. A conflicting commit is retried rather than dropped, so concurrent writers do not lose versions. Each document is stored as a sorted N-Triples file under the configured path prefix — Northwind order 10248 as graphs/orders/10248.nt:

<https://localhost:4443/orders/10248/> <http://purl.org/dc/terms/title> "10248" .
<https://localhost:4443/orders/10248/#this> <https://schema.org/identifier> "10248" .
<https://localhost:4443/orders/10248/#this> <https://schema.org/orderDate> "1996-07-04"^^<http://www.w3.org/2001/XMLSchema#date> .
…

To disable versioning, remove the lds:versioningRepository triple and restart — the repository configuration is read at startup.

Historical versions

GET with the ?version=<commit-sha> query parameter serves the document as it was at that commit. The response carries a Memento-Datetime header with the commit datetime, the commit SHA as the ETag, and immutable Cache-Control — a historical version never changes.

Historical versions are read-only: only acl:Read is advertised in the Link headers and write methods answer 405 Method Not Allowed. In the user interface a historical version page shows a notice banner with a link back to the current version.

Historical versions are also outside the scope of SPARQL: the dataspace's RDF dataset holds only the current version of every document, while the history lives solely in the Git repository. Queries on the SPARQL endpoint therefore always answer over the latest versions.

GET /orders/10248/?version=8c9f2b1 HTTP/1.1
Host: localhost:4443
Accept: text/turtle

HTTP/1.1 200 OK
Content-Type: text/turtle
Memento-Datetime: Tue, 01 Sep 2026 14:03:59 GMT
ETag: "8c9f2b1"
Cache-Control: public, max-age=31536000, immutable
Link: <https://localhost:4443/orders/10248/>; rel="original"

TimeMap

GET with the ?timemap query parameter serves the document's version history. In RDF formats the TimeMap is described with PROV-O: a prov:Collection of prov:Entity mementos, each prov:specializationOf the document, prov:generatedAtTime its commit datetime, and prov:wasRevisionOf its predecessor. Requesting application/link-format serves the serialization RFC 7089 requires.

Order 10248's history with two versions, as PROV-O:

@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .

<https://localhost:4443/orders/10248/?timemap> a prov:Collection ;
    prov:hadMember <https://localhost:4443/orders/10248/?version=4d1e7aa>,
        <https://localhost:4443/orders/10248/?version=8c9f2b1> .

<https://localhost:4443/orders/10248/?version=4d1e7aa> a prov:Entity ;
    prov:specializationOf <https://localhost:4443/orders/10248/> ;
    prov:generatedAtTime "2026-08-30T10:12:04Z"^^xsd:dateTime .

<https://localhost:4443/orders/10248/?version=8c9f2b1> a prov:Entity ;
    prov:specializationOf <https://localhost:4443/orders/10248/> ;
    prov:generatedAtTime "2026-09-01T14:03:59Z"^^xsd:dateTime ;
    prov:wasRevisionOf <https://localhost:4443/orders/10248/?version=4d1e7aa> .

and the same TimeMap as application/link-format:

<https://localhost:4443/orders/10248/>;rel="original",
<https://localhost:4443/orders/10248/?timemap>;rel="self";type="application/link-format",
<https://localhost:4443/orders/10248/?version=4d1e7aa>;rel="memento";datetime="Sun, 30 Aug 2026 10:12:04 GMT",
<https://localhost:4443/orders/10248/?version=8c9f2b1>;rel="memento";datetime="Tue, 01 Sep 2026 14:03:59 GMT"

A TimeMap of a document with no versions answers 404 Not Found. A non-versioned document answers 406 Not Acceptable when application/link-format is requested.

The Memento hypermedia uses IANA-registered relation types: the document advertises its timemap and timegate in Link headers, a historical version links back to the document with rel=original, and the TimeMap identifies itself with rel=self.

TimeGate

GET with the ?timegate query parameter performs datetime negotiation: the version closest to the Accept-Datetime request header is answered with 302 Found and its URI in Location, with ties resolved towards the more recent version. Without Accept-Datetime, the most recent version is selected. The response carries Vary: accept-datetime.

GET /orders/10248/?timegate HTTP/1.1
Host: localhost:4443
Accept-Datetime: Mon, 31 Aug 2026 00:00:00 GMT

HTTP/1.1 302 Found
Location: https://localhost:4443/orders/10248/?version=4d1e7aa
Vary: accept-datetime
Link: <https://localhost:4443/orders/10248/>; rel="original"

Restore and diff

From the version history dialog, a document can be restored to an earlier version — restoring creates a new commit rather than rewriting history, so the intervening versions remain in the TimeMap — and any two versions can be compared as a diff rendered on the document page.

Management

The version history dialog lists a document's versions and drives the restore and diff actions.