Skip to main content

Pygitweb

Gitweb reimplementation using Python, FastAPI, and Pygit2, ported from git/gitweb/gitweb.perl.

Config

Settings load from ~/.pygitweb/settings.json (or PYGITWEB_SETTINGS_CONFIG), PYGITWEB_* environment variables, and optionally a .env file in the working directory via pydantic-settings. See src/pygitweb/config.py and settings.schema.json for the full schema. Common ones:

  • PYGITWEB_PROJECTROOT — absolute path to directory containing git repositories (default: $HOME)
  • PYGITWEB_PROJECTS_LIST — directory to scan, or path to a project-list file (default: PROJECTROOT)
  • PYGITWEB_EXPORT_OK — filename that must exist to allow export (e.g. git-daemon-export-ok); empty = no check
  • PYGITWEB_SITE_NAME — site name in titles (default: PyGitWeb)
  • PYGITWEB_GIT — path to the git executable (default: git)
  • PYGITWEB_AUTH — 0 / false / off disables auth; 1 / true / on enables it. If unset or empty, auth defaults to on.
  • PYGITWEB_AUTH_CONFIG — path to the auth JSON file (default: ~/.pygitweb/auth.json). Generated to a reasonable default if not present.
  • PYGITWEB_SETTINGS_CONFIG — path to the settings JSON file (default: ~/.pygitweb/settings.json). Created from the environment on first run if missing.
  • PYGITWEB_ENABLED_PLUGINS — installed plugins to load. The settings page can change this list and reload plugins immediately (POST /settings/pygitweb/plugins).
  • PYGITWEB_API_KEY_MAX_COUNT — maximum Smart HTTP API keys each local user may hold (default: 2).
  • PYGITWEB_API_KEY_EXPIRY_DAYS — lifetime of a newly issued API key, in days. 0 means the key does not expire (the default).
  • Browser login: GET /login (optional ?next=/path), POST /login with form fields username, password, next; sets an HttpOnly cookie with an opaque in-process session id (also accepted by protected routes via Authorization: Bearer). Sessions are wiped when the process exits. GET /logout?next=/ revokes the session and clears the cookie.
  • Account and Smart HTTP keys (auth required): GET /user is the account page. POST /user/keys with JSON {"id": "...", "readonly": false} issues a key and returns the plaintext secret once; only its scrypt hash is stored. readonly: true allows fetch but not push. DELETE /user/keys?id=<id> revokes one key. Clone and push attempts are appended to ~/.pygitweb/smart_http.log.

Routes

Load the /docs page for a detailed view of routes (below the readme).

No-project routes

  • GET /: project list
  • GET /index: plain text project index (path, owner)
  • GET /opml: OPML feed list
  • GET /project/{name}: Project dispatch (See actions table)

Hook management routes

  • GET /project/{name}/hooks: JSON {hooks: [...samples...], bundles: [...]} with current status (installed, not_installed, different) for every pygittools sample and bundle
  • POST /project/{name}/hook?name=<sample-or-bundle>&op=<add|remove|check>: install, remove, or check one sample or bundle. name may be a sample filename (e.g. post-receive.notify) or a bundle name (e.g. update, which expands to post-commit.notify + post-receive.notify). add/remove refuse to clobber a custom hook with different content (returns 409)

Actions

Action Query parameters URL Description
summary (default) or a GET /project/{project} Project summary (description, owner, HEAD, tree link).
tree h, f GET /project/{project}?a=tree&h=...&f=... Directory listing (tree).
blob h, f GET /project/{project}?a=blob&h=...&f=... File view (HTML).
blob_plain h, f GET /project/{project}?a=blob_plain&h=...&f=... Raw file download.
log h, updates, pf GET /project/{project}?a=log&h=... Commit log. Supports long-poll subscribe.
shortlog h, updates, pf GET /project/{project}?a=shortlog&h=... Shortlog. Supports long-poll subscribe.
history h, f, updates, pf GET /project/{project}?a=history&h=...&f=... History of a file or path. Supports long-poll subscribe.
heads updates, pf GET /project/{project}?a=heads List branch heads. Supports long-poll subscribe.
tags updates, pf GET /project/{project}?a=tags List all tags. Supports long-poll subscribe.
tag h GET /project/{project}?a=tag&h=... Single tag view (tag ref or hash).
commit h GET /project/{project}?a=commit&h=... Commit information.
commitdiff h GET /project/{project}?a=commitdiff&h=... Commit diff (unified diff rendered with diff2html).
patch h GET /project/{project}?a=patch&h=... Single-commit patch (plain text).
patches h, hb GET /project/{project}?a=patches&h=...&hb=... Multi-commit patches for range hb..h (plain text).
blobdiff h, hb, f, fp GET /project/{project}?a=blobdiff&h=...&hb=...&f=...&fp=... Blob diff (two versions of a file) rendered with diff2html.
blobpatch h, hb, f, fp GET /project/{project}?a=blobpatch&h=...&hb=...&f=...&fp=... Blob diff as plain unified diff.
remotes — GET /project/{project}?a=remotes List repo remotes.
object h GET /project/{project}?a=object&h=... Show object by type (commit, tree, tag, or blob).
blame_raw h, f GET /project/{project}?a=blame_raw&h=...&f=... File blame as JSON: array of {commit_id, commit_msg, commit_author, time, line_range}. Contiguous lines from the same commit are merged.
blame h, f GET /project/{project}?a=blame&h=...&f=... File blame HTML view (syntax-highlighted source, line numbers, commit links per line range; hover shows author and date).
blame_incremental — TODO
blame_data — TODO
rss — TODO
atom — TODO
search patterns, paths, globs, heading, sort, max_count, multiline GET /project/{project}?a=search&patterns=... Ripgrep search (via python-ripgrep); returns JSON. paths are relative to the project tree and cannot escape it.
search (page) — GET /project/{project}/search Per-project search UI page.
search_help — TODO

Blog, boards, and tasks

  • GET /project/{project}/blog/: markdown blog page when articles/manifest.json exists at HEAD. Otherwise the summary link stays disabled.
  • GET /project/{project}/blog/raw/{path}: raw blob from HEAD for the blog viewer (markdown, manifest, and assets). Paths that escape the tree return 404.
  • POST /tasks/move?project=<name>&board=<source>&task=<ref>&target=<board>: move a task onto another board, such as a sprint board. Column headers on the board page show a running points total. points on create and update accepts 0, 1, 2, 3, 5, 8, 13, or 21; any other value is stored as unestimated.
  • POST /tasks/archive?project=<name>&board=<source>&task=<ref>: move a task to the archive board.
  • POST /tasks/delete?project=<name>&board=<source>&task=<ref>: delete a task.

Query parameter short names (CGI mapping)

  • p → project
  • a → action
  • f → file_name
  • fp → file_parent
  • h → hash
  • hp → hash_parent
  • hb → hash_base
  • hpb → hash_parent_base
  • pg → page
  • o → order
  • s → searchtext
  • st → searchtype
  • sf → snapshot_format
  • opt → extra_options
  • sr → search_use_regexp
  • by_tag → ctag
  • ds → diff_style
  • pf → project_filter
  • updates → long-poll subscription flag (true parks the request up to 30s; returns 200 OK with an empty body on timeout/shutdown, or the action data when the queue is notified)

Live updates (long polling)

Supported actions (history, log, shortlog, heads, tags) accept updates=true to subscribe to the project's change queue. The request is held for up to 30 seconds:

  • A POST /_internal/notify?project=<name> (typically from a server-side git hook) wakes matching subscribers, who then receive the freshly-rendered action response.
  • If no notification arrives in 30 seconds (or the server is shutting down), the response is 200 OK with an empty body.
  • Combine with pf=<prefix> to subscribe to every project under a path prefix instead of just the URL project (the action is still rendered for the URL project).

The notify endpoint is intended for loopback use by pygittools post-receive hooks (see pygittools/hook_samples/post-receive.notify). The server enforces loopback-only access and rejects unknown projects; production deployments should still restrict it at the reverse proxy.

Hook management

The project summary page exposes a Hooks row that lists installed hooks and lets you install or remove pygittools samples, including the update bundle: post-receive.notify (feeds the change queue when refs are pushed) and post-commit.notify (feeds the change queue when a working clone of this repo records a local commit). Either is sufficient to wake long-poll subscribers; installing both covers server-side and client-side commit paths.

The same operations are available programmatically via POST /project/{name}/hook?name=<sample-or-bundle>&op=<...>. Bundle status is INSTALLED only when every member is installed, DIFFERENT if any member's path holds a custom hook (the bundle then refuses to install or remove anything to preserve the custom hook), otherwise NOT_INSTALLED. When PYGITWEB_AUTH is enabled, add and remove require a valid access token (Authorization: Bearer … from POST /token, or the cookie from POST /login); check is always allowed.

Metadata

Release files for pygitweb 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pygitweb 0.3.0
File Size Uploaded
pygitweb-0.3.0.tar.gz 707.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pygitweb 0.3.0
File Interpreter ABI Platform
pygitweb-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.5 MB

Release files / pygitweb-0.3.0.tar.gz

Download URL pygitweb-0.3.0.tar.gz
Size 707.3 kB
Tags Source
SHA-256 checksum
How to use checksums
eec7845bdba7644a9d94c42ffd23cb8d8b627b5c307ddb35363f185d1086cac4
BLAKE2b-256 checksum
How to use checksums
7242c1b11fd4ea94424f7030147189ef8f03ade50e7e82b5884488c6b7487091
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / pygitweb-0.3.0-py3-none-any.whl

Download URL pygitweb-0.3.0-py3-none-any.whl
Size 774.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e1bc92a144bd2054813b6c6067aad7f809f88c899cf8f8486c19709f0c2773cf
BLAKE2b-256 checksum
How to use checksums
b557e66976a5c558044278bf822b85f3bbec0d4c5ebd0b32bf9d6a80f85d0f9a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page