The static site is a website of the repository that gitsocial mirror and gitsocial push build in a bucket, as static files that the browser reads directly: timeline, issues and boards, pull requests, releases, code, search, analytics.
Mirror · Publish · Customization · HTML pages · Fork pull requests · Design · Testing · Reference
For a project on a forge, one command clones the project, imports its issues, pull requests, releases and discussions, pushes data and code, and builds the site:
gitsocial mirror https://github.com/owner/repo s3://<endpoint>/<bucket>/<prefix> --url https://your-domain/
gitsocial mirror # later, from the workspace: refresh
--url is the site's public URL and enables the HTML pages.gitsocial remote add s3://<endpoint>/<bucket>/<prefix> # once, see S3.md
gitsocial config site set publish true
gitsocial push
publish is off by default. It is in the pushed config ref, so a plain git push that carries that ref also rebuilds the site.--no-site skips the rebuild for one push. git config gitsocial.pushSite false opts a machine out. Neither turns the site on.gitsocial push --site-only [remote...] rebuilds the site and sends no refs. It fails when publish is off or the remote is not s3.gitsocial push --full detaches it and the site returns.gitsocial config site set <key> <value> # or in the TUI under Configuration, Site
gitsocial config site list
Values are in the site object of the core config ref and reach the bucket as .gitsocial/site/site-config.json on the next push. A value that fails validation uses its default. The last four keys belong to the HTML pages.
| Key | Example | Validation |
|---|---|---|
title | "My Project" | plain string, trimmed, up to 200 characters |
description | "One sentence for search results" | plain string, trimmed, up to 300 characters |
accent, accentDark | "#0a7" | #rgb or #rrggbb |
favicon | favicon.png | shown beside the sidebar title: a key relative to the site root (upload with gitsocial remote put) or an absolute https:// URL, up to 500 characters |
image | og-card.png | og:image for every page: a key relative to the site root (upload with gitsocial remote put) or an absolute https:// URL, up to 500 characters |
forkPullRequests | true | true or false, default false; see Fork pull requests |
url | https://example.com/ | absolute https:// base (http:// for localhost only), no query or fragment, up to 500 characters |
publish, pages | true | true or false, both default false |
filesInclude, filesExclude | "internal/**" | comma-separated repo-relative globs; ** spans segments; a leading / or a .. segment is dropped |
Only url, publish and pages can differ per remote; the other keys are the same for all remotes of the repository. An override is in the local git config as remote.<name>.gitsocial-site-<key>, rebuilds the full site of that remote on the next push, and applies to pushes by remote name, not by URL.
gitsocial config site set publish false --remote backup
gitsocial config site list --remote backup # effective values
HTML pages are crawlable pages that a browser shows without JavaScript; with JavaScript on, a page starts the app in the same browser tab.
gitsocial config site set pages true
gitsocial config site set url "https://example.com/" # absolute base for canonicals, OG tags and the sitemap
Effective when publish, pages and a valid url are all set. Every push then maintains:
older → and ← newer links. A full page holds a complete page of rows, and later pushes do not rewrite it; the newest rows are on the head page, index.html. Milestones and sprints are in issues.commits/<n>.html#c-<sha12>.f/<path>.html: markdown (.md, .markdown, .mdown, .mdx) and the extensionless convention documents (LICENSE, CONTRIBUTING, CHANGELOG and their siblings). The selection excludes documents under a dotdir or a vendored, test or fixture directory, and also submodules, symlinks, the root README and paths with a space or one of @ : # ? %. filesInclude and filesExclude change the selection.sitemap.xml with every indexable page, and Atom feeds: feed.xml and one per type directory, memos and commits excluded.What a page shows:
#commit:, #branch:, #tag:, #file:, #list:) and each bare URL linked; a mention stays text. Only the README and file pages, the bucket owner's own content, render as markdown, with raw HTML rebuilt against an allowlist.noindex,follow.<title> is unique, and every description is prose with the markdown stripped.On a large repository, several pushes can be necessary to write every page; GITSOCIAL_SITE_PAGES_BUDGET limits the item pages that one push writes (S3.md). Setting pages false or removing url deletes the pages on the next push.
gitsocial config site set forkPullRequests true
With publish and forkPullRequests set, each push writes the open pull requests of registered forks whose base is this repository under .gitsocial/forks/: one pack per head that gitsocial fetch brought, and a manifest. The app lists each one with a fork chip and the fork URL, and reads its pack when a reader opens it. A head not fetched yet, or one whose pack is over 32 MB, shows the pull request with a notice in place of the diff. Setting the key to false deletes the area on the next push. A lock key in the area, put with gitsocial remote put .gitsocial/forks/lock <file>, freezes it: the push writes and deletes nothing there and prints one note.
The site is the app shell, the pre-rendered pages and the two stylesheets. Five rules hold across all three.
.mdx documentation site.The tokens are declared once, in pages-core.css; site_pages_tokens_test.go fails on a spacing value off the scale or a token declared elsewhere. sitetest/parity_fixtures.json records the markup and wording that the page layer and the app share. A visual change includes its golden and baseline update in the same commit.
scripts/site-test.sh # the browser battery
go test -tags sitetest -timeout 30m ./library/core/site/ # the same from go test; skipped without node
bin/locals3 -root <dir> # serve a pushed site locally, see S3.md
GS_STYLES_UPDATE=1 node library/core/site/sitetest/verify_styles.js # recapture the style baselines
go test -tags sitetest -run TestSiteShapeGoldens ./library/core/site/ -update # regenerate the goldens
library/core/site/sitetest/. The browser suite runs at release, and no test in go test ./... covers the browser side.verify_styles.js runs under a DOM shim. verify_styles.js drives a real Chrome, compares computed styles and structure on ten routes in both themes to sitetest/styles/, and checks that the main routes fit a 390 px viewport.shapes.sh builds six repository-shape fixtures: a non-main default branch, an .mdx docs site, an empty repository, code only, a 6,000-file tree, binary and LFS objects. TestSiteShapeGoldens compares their screenshots at 1280 and 390 px to sitetest/goldens/.chrome.js finds Chrome from a CHROME override or a list of absolute paths. Without Chrome the style suite and the goldens skip; release.sh fails in its first step when it cannot find Chrome.The pages are at the prefix root, next to the shell; the cache classes are in S3.md.
| Key | Content | Cache |
|---|---|---|
index.html | front page while the page layer is on, otherwise the shell; its asset references carry the shell revision | no-cache |
.gitsocial/site/shell/<rev>/ | the shell assets of one binary version; the current and previous revisions are kept, older ones swept on a shell change | immutable |
i/<short>.html | one page per top-level item | no-cache |
issues/, prs/, posts/, releases/, memos/ | index.html head plus full <n>.html pages | head no-cache, full pages immutable |
commits/index.html, commits/<n>.html | the default-branch commit list | no-cache |
f/<path>.html, f/index.html | file pages and their list | no-cache |
sitemap.xml, sitemap-head.xml, sitemap-<n>.xml | the sitemap, an index over parts when it is large | head no-cache, parts immutable |
robots.txt | Allow: / and the sitemap location | no-cache |
feed.xml, <dir>/feed.xml | Atom feeds | no-cache |
.gitsocial/forks/manifest.json | the open fork pull requests: the entries of each one, the pack of its head and the bytes of the area; written last, version 1 | no-cache |
.gitsocial/forks/pack/pack-<hash>.{pack,idx} | the objects reachable from one fork head and from no bucket ref, plus the review commits of its pull request; non-thin, offset deltas, 32 MB at most | immutable |
.gitsocial/forks/lock | the owner's marker: while it exists the push writes and deletes nothing under .gitsocial/forks/ | no-cache |
Item pages and full list pages are rewritten only when sitePagesVersion in site_pages.go changes; the commit that bumps it says why. Everything else is rewritten on every push.
The app reads the artifacts under .gitsocial/site/, and the bucket's ref list at .gitsocial/refs.json (S3.md), in place of object walks.
| Key | Content |
|---|---|
version | shell version marker, a hash of the raw assets |
items/<ext>/ | per-extension metadata index: full shards, a mutable head, a manifest; an adopted copy's entry also names its original author and time |
bodies/<ext>/ | message bodies, loaded on demand |
items/code/ | one index of plain commits across code branches, with parent shas and no bodies |
pages.json | page-layer manifest: schema version, consumed tips, bootstrap cursor, list and commits partitions, the sidebar's item counts |
site-config.json | the customization values |
pm-config.json | the resolved PM board |
stats.json | a stats blob written by the CLI from the workdir |
tags.json | each tag's commit, date, author, previous tag and its commit, the commit count since it, and whether its range document is written, in display order, from the local object database |
ranges/v1/<prev>..<commit>.json | one range document for each pair of adjacent tag commits: the merge base, the commits since the previous tag (up to 2,000) and the files changed (up to 1,000); immutable |
push-state | skip digest for push-time maintenance |
.gitsocial/forks/ | the fork area: manifest.json lists the open pull requests of registered forks, each entry with the fields of a shard entry, the fork URL, the head and its pack; pack/pack-<hash>.{pack,idx} holds the objects of one head that no bucket ref reaches; lock freezes the area for the push |
The shell and the pages:
pages-core.css is inlined into every page head, carries no url(), and bumps sitePagesVersion when it changes. pages-full.css loads after first paint, and the app uses the same two stylesheets.:root override after the inlined core, so the shell version comes from the binary alone.index.html has two owners: the page layer while it is effective, the shell otherwise. Every rebuild with an effective page layer writes it again.#commit:, #branch:, #tag:, #file:, #list:) renders as <a class="hash"> with the ref as its text on the page and in the app. The grammar is protocol.FindRefs in Go and REF_LINK_RE in gs-render.js, and refTokens in parity_fixtures.json pins the two, with protocol.FindMentions and MENTION_RE for a mention. The app links a mention to #/search/author:<email>; the page shows it as text. A ref inside a URL or a markdown link stays text, and a trailing period or comma stays outside the ref.i/<short>.html, and any other ref links its route through index.html; a bare URL in a body is an external link with rel="nofollow ugc", and no markup from a body reaches the page. pageBodyRefs in parity_fixtures.json pins the anchors of a body on both renderers.itemPageMeta in parity_fixtures.json pins the rows on both renderers.assignee:, reviewer:, label:), each ref of a trailer row to its route, a tip or merge hash to its commit, a browsable cross-repository ref or repository URL to the URL as written, the branches of a pull request card and every branch chip to the branch log, the file chip of a feedback to its lines on the head branch (the old lines on the base), a release head to its tag page, and the hash and parents of a commit page to their commits. The author bit, the feedback file chip and the release head are shared with the page, which links them to the same routes through index.html; metaRow, feedbackFileRefs and detailHeads in parity_fixtures.json pin the three. The page links the branch pair of a pull request to the same routes, and shows a fork head as its repository and branch. A raw pane, a quoted excerpt and a search snippet stay text.#commit:<hash> route with no branch resolves by hash through the item indexes first, so an item opens as its detail and a code commit as the raw commit page.headerAction has one definition in two places, gs-core.js and core/cache/actions.go, asserted equal on sitetest/action_fixtures.json. The timeline of the page layer lists posts only.tags.json entry only when its sha is the tag's sha in refs.json; for any other tag it reads the tag objects. It shows commit counts only when the file covers every tag and the previous tag of each entry is the next row.refs.json.tags.json entry flags one for the commit of the previous row, and walks objects otherwise. A push writes the range documents before tags.json, and flags only the documents that it wrote or found flagged for the same pair. A shallow clone writes none.compareSiteTagsDesc and compareTagsDesc, and a hash suffix (v<version>.<hash>) is not part of the version. Tags that differ only by that suffix are in date order.git rev-list base..head does, newest committer time first. The number of reads follows the range, not the history. A range longer than 2,000 reads shows as truncated, with no commit from under the base. A 12-character tip of a pull request expands through the code index, then the pack indexes, then a bounded walk of its branch; a short that names two objects does not expand. The merge head of a merged pull request comes from refs/gitmsg/review/merged/<hash> in refs.json first, so a squash or rebase merge whose head branch is gone still shows its files.parity_fixtures.json. The page reads the range from the local object database of the pusher, the merged head ref first for a merged pull request, and lists up to 100 files and 500 rows; the app shows the rest.refs.json, and commits and the item counts from pages.json, which carries the item counts only after a complete pass.Commits pages:
/commits route reads the pagination from pages.json and renders the rows that the page shows.no-cache and re-derived after a rebase or force-push. The frontier is the newest row of the full pages. Every pass finds the recorded frontier sha in the current list, with the same number of rows below it; when either check fails, the pass re-derives every full page.Page entry:
<meta name="gs-route"> and its base in data-base on #gs-page. gs-upgrade.js loads the shell from that base.<html> with gs-boot, which hides the page during boot; the front page is not hidden.location.hash that names a route has priority over the page's own route; a bare anchor stays on the page.Index maintenance:
items/<ext>/ and bodies/<ext>/: content-hash-keyed full shards, a mutable head and a manifest.items/code/ takes its tip as a digest over the sorted branch tips, attributes each commit to a branch by the reader's own rule, and repairs the index when the tip changes.Every ref-moving push to an s3 remote runs the site rebuild as its part of push maintenance (S3.md), through the site.PostPushMaintenance hook that the CLI gives the remote helper.
.gitsocial/site/push-state records the last full pass: the shell version, a digest over the ref listing and the page layer's state. A matching marker skips the pass.