Where the board is kept¶
Your issues, people, sync records and release records are the board. By
default git-board keeps them in git, beside your code, under
refs/board/*. A clone can keep them elsewhere instead: in a PostgreSQL
database, in a folder of Markdown files, or on the forges alone. Your code stays in git whatever you
choose. How the data flows shows the layers.
Choosing¶
| Git (default) | PostgreSQL | Markdown files | The forges alone | |
|---|---|---|---|---|
| Who sees a write | a clone, until it pushes | everyone, at once | whoever has the commit | everyone, once it reaches the forge |
| Offline work | yes | only with a local server | yes | no |
git board pull, push, start over |
yes | no: nothing to move | no: commit and push the folder | no |
Project numbers (CO2-3075) |
yes, refs/board/seq on origin |
yes, a row in the database | yes, .board/numbers, on the default branch |
the forges' numbers only |
| Forge sync | yes | yes, the same | yes, the same | yes: it is the store |
| Lists and search | a sqlite file per clone | one index on the server | a sqlite file per clone | a sqlite file per clone |
| Read and edit by hand | no | no | yes, a file per issue | on the forge |
| History of an issue | every operation | every operation | every operation | what the pulls saw |
Pick PostgreSQL when a team wants one shared board with no pull or push, or a board that people who never clone can reach. Pick Markdown files when the board should go with the code: branches, pull requests and reviews, readable without git-board. Pick the forges alone when you want no store of your own: the board is the forges' copy, filled by each pull, and git-board is a way to work on their issues.
Settings › Datastore¶
In the web UI, Settings › Datastore shows where the board is kept now and what that datastore can do.
- Choose PostgreSQL and paste a URL:
postgres://user:password@host:5432/database; or Markdown files and a folder of the checkout,issues. - Copy the board from git copies every issue, person and record this clone has in git into it. Every id is kept, so the links and markers sync left on the forges stay true. A second copy writes only what is new; the git refs aren't touched.
- Keep the board here copies first when it can, then switches: the clone reads the board from there at once, with no restart. A value no datastore can open is refused, and the board stays where it was.
The URL is kept in this clone's git config (board.datastore), never in
.git-board.toml: it may hold a password. The page never shows the
password again.
From a terminal¶
git config board.datastore postgres://me@db.example.org/board
git board datastore copy # the board in git, into the database
git board datastore # where the board is kept, and what it can do
GIT_BOARD_DATASTORE sets it too, over git config: for a server started
by a service manager, say. While it is set, the Settings tab can't change
the datastore.
Back to git: git config --unset board.datastore, or choose Git in the
tab. What was written in PostgreSQL meanwhile stays there: copy it back
by hand only if you need it (see Left open in ADR 0002).
PostgreSQL¶
- The tables are made on first use, in two schemas:
git_board(the board:entities,packs,counter) andgit_board_index_v<version>(lists and search, one schema per version of its tables, so two git-board versions can share the database; an older one's is left for you to drop once nothing reads it, as isgit_board_index, older builds'). The user needs the right to create schemas in that database the first time, and to read and write them after. - One database is one board. Give each project its own database.
- Who you write as is this clone's,
board.mein its git config, as for any board. Choose it in Settings › You. - Numbers. Settings › Numbers starts them as on a git board; the counter is a row in the database, so numbering needs no push, and a sync numbers what has no number yet.
- One sync at a time. A forge sync holds the board's lock in the database: a second one, from any machine, is refused while it runs ("another sync runs"), and a sync killed midway holds nothing.
- Backups are the database's:
pg_dumpthe two schemas. - Tests.
make test-pgruns the PostgreSQL tests againstGIT_BOARD_TEST_POSTGRES, a DSN whose user may create databases.
Markdown files¶
git config board.datastore markdown:issues
git board datastore copy # the board in git, into issues/
The folder, from the checkout's top, holds one file per issue,
NNN-slug.md, as git board export --md writes it, and the board itself
in issues/.board/: each issue's, person's and record's operations, one
line per write, in <kind>/<id>.jsonl. Commit both with the code.
To keep the issues in a repository of their own, give the full path of
its checkout: markdown:/home/me/project-issues. The issues are then
text files in that repository, which you edit, commit and push there.
Numbers are handed out on that repository's default branch. Settings ›
Datastore takes the same full path, and refuses a path up with ...
- The files follow the board. After each write here, a forge sync's too, every file whose issue changed is written again.
- Edit a file by hand: the title, the description,
state, the fields, labels and assignees, a comment's text; or add a comment under a heading### Your name · 2026-11-04T10:00:00Z. The next write reads your edits in, as you, and so doesgit board import. A new file with a title and noidis a new issue. An issue changed here after the file'supdatedkeeps its own values: the edit is reported as a conflict, and the file is written again from the board. - A heading in your text is safe. The line
<!-- git-board comments -->ends the description, and each comment ends at its marker, so a### …line inside either stays text. - Branches merge their boards.
.board/.gitattributesmerges the.jsonlfiles with git's union driver: two branches' writes to one issue both stay, in the order every clone agrees on. A conflict in a.mdfile needs no care: take either side, the next write renders it again. - A server and a command at once take turns: each write reads the
hand edits in, writes and renders under
.board/board.lock, so no edit is read in twice..board/.gitignorekeeps the lock files out of git. - Numbers are handed out on the default branch. Settings › Numbers
(or
git board numbers init) starts them as on a git board; the counter isissues/.board/numbers, the filerefs/board/seqholds, committed with the rest. Numbering (Number them now, a sync,git board numbers) writes it only while the checkout is on the default branch: origin'sHEAD, elsemain, elsemaster. On another branch the counter is read, and numbering says why it waits; the branch's issues take their numbers once it is merged and someone numbers on the default branch. A file is named by its issue's number, else by the forges' number, else by the id's first 7 digits. - The counter never merges line by line.
.board/.gitattributesgives/numbersgit's binary driver: only the default branch writes it, so a merge from a branch takes the default branch's. Two clones that numbered on the default branch at once conflict on it when one pulls: take theirs (git checkout --theirs issues/.board/numbers), commit, and number again; your issues take the next numbers. The union driver would keep one number twice, and a text merge would write conflict markers in the file.
Importing Markdown¶
On any board, git board import --md <dir> reads a folder that
git board export --md <dir> wrote, or files written the same way. A
file whose id is an issue here gives it your edits; a file with no
id, or an id this board lacks, is a new issue with the author, comments
and times it names, and its new id is written back into it. People are
found by name or login, and made when there is none. Other Markdown files
in the folder are left alone.
In the web app, select Download the issues as Markdown in Settings › Datastore. You get the same files in a zip.
A read-only site¶
git board export --html <dir> writes the issues as a static web site:
index.html lists them, open first, and NNN.html shows each issue with
its comments. The text is cleaned as the web app cleans it, and no page
runs a script, so any web server can serve the folder. Nothing on the
site writes to the board.
To publish it on GitHub Pages, run the command in a workflow, then
upload the folder. The workflow needs the board's refs, so fetch them
first. The recipe installs git-board from main, because no release has
export --html yet. Pin a release tag once one has it.
on: { push: { branches: [main] }, schedule: [{ cron: "0 * * * *" }] }
permissions: { contents: read, pages: write, id-token: write }
jobs:
site:
runs-on: ubuntu-latest
environment: github-pages
steps:
- uses: actions/checkout@v4
- run: git fetch origin "+refs/board/*:refs/board/*"
- run: go install github.com/guilbep/git-board/cmd/git-board@main
- run: git board export --html site
- uses: actions/upload-pages-artifact@v3
with: { path: site }
- uses: actions/deploy-pages@v4
A public site shows every issue, a private project's too. Publish a private project's site only where its readers already see the issues.
The forges alone¶
The board lives on the forges with sync on (Settings › Sync). This clone
keeps a copy in .git/board/forge-copy.git, filled by each sync, and
nothing under refs/board/*.
- Each write goes to the forges at once.
git board servesends an issue's change as soon as you make it, and tries again every 30 seconds while a forge doesn't answer. Writes from the command line go with the nextgit board sync. - The copy is disposable.
git board datastore dropthrows its issues away, and the next sync pulls them back; the people and the release records, which no forge keeps, stay. It refuses while an issue has a write made here since the last whole sync (git board sync);--forcethrows those away too. - What comes back is what the forges show: an issue or a comment git-board posted comes back with the line it was posted with (From Pierre, 2026-11-04, in git-board.), as anyone's text is.
- No project numbers of git-board's own: the issues go by the forges' numbers.
- A board that syncs with a second forge has a store of its own to keep: choose git or PostgreSQL then.