provcat.stores.github

GitHub repositories: github://owner/repo/path?at=branch:main.

The selector names a git ref with branch:, tag: or commit:. Without a selector the store uses the default branch of the repository.

?at=ts:TIME names the commit that the default branch had at TIME, and ?at=ts:BRANCH@TIME the commit that BRANCH had. The store walks the branch’s first parents from its head, and takes the first commit whose committer date is at or before TIME. First parents are the commits of the branch itself, so a commit made on another branch and merged later by a merge commit is left out. Git records when a commit was made, not when it was pushed, so a commit made on Monday and pushed on Wednesday counts from Monday. docs/design.md lists the other cases where the answer differs from what the branch showed. A branch: must be a branch and a tag: must be a tag; the store asks the git refs API, so a tag of the same name cannot stand in for a branch.

Every fetch first pins the ref to a commit. The cache key holds the commit, so a cached file never goes stale and needs no check::

<cache>/github/<owner>/<repo>/<commit>/<path>

A URI with ?at=commit:<40 hex digits> needs no network call when the cache holds it. Pinning a branch or tag records the commit in the cache, so offline mode can use the last commit it saw. Offline mode also serves a file inside a folder that was fetched whole.

A single file comes from the contents API, which serves files up to 100 MB. A folder comes from one tarball of the commit, which costs one API call however many files it holds. The tarball unpacks only plain files and folders; links, devices and names that climb with .. are left out. A folder that holds two names one disk could store as one, for example Guide.md and guide.md, is refused.

Git LFS: both the contents API and the tarball give the pointer file of a file that Git LFS keeps, not its content. So after a download the store looks for pointer files, which are under 1024 bytes, and asks the Git LFS batch API of the repository for their content, 100 objects at a time. It checks the size and the SHA-256 of each object against its pointer. The LFS server is on the web host of GitHub, github.com for api.github.com, and the token goes there as well. The download links that the batch API gives carry their own headers, and the token never goes to them. A repository that names another LFS server in .lfsconfig is not supported, and neither is a pointer that names Git LFS extensions. A file that looks like a pointer but whose object the LFS server does not have fails the fetch. list gives the size of the pointer of an LFS file, because the contents API lists it so; stat gives the size of the content.

Options:

The token goes only to the host of api_url and, for Git LFS, to the web host of the same GitHub. A redirect to another host, for example the tarball download host, does not carry it.