Gitweb reimplementation with FastAPI and Pygit2
Project description
Pygitweb
Gitweb reimplementation using Python, FastAPI, and Pygit2, ported from git/gitweb/gitweb.perl.
Setup
uv sync --package pygitweb
Or install everything in the workspace (all members + dev tools):
uv sync --all-packages --group dev
Run
From the repository root (after uv sync), with the workspace .venv activated:
uvicorn pygitweb.main:app --reload --host 0.0.0.0 --port 8000
Or:
python -m pygitweb.main
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 pygitweb/config.py and pygitweb/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 checkPYGITWEB_SITE_NAME— site name in titles (default:PyGitWeb)PYGITWEB_GIT— path to the git executable (default:git)PYGITWEB_AUTH—0/false/offdisables auth;1/true/onenables 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.- Browser login:
GET /login(optional?next=/path),POST /loginwith form fieldsusername,password,next; sets an HttpOnly cookie with an opaque in-process session id (also accepted by protected routes viaAuthorization: Bearer). Sessions are wiped when the process exits.GET /logout?next=/revokes the session and clears the cookie.
Routes
Load the /docs page for a detailed view of routes (below the readme) if in debug mode.
No-project routes
GET /: project listGET /index: plain text project index (path, owner)GET /opml: OPML feed listGET /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 bundlePOST /project/{name}/hook?name=<sample-or-bundle>&op=<add|remove|check>: install, remove, or check one sample or bundle.namemay be a sample filename (e.g.post-receive.notify) or a bundle name (e.g.update, which expands topost-commit.notify+post-receive.notify).add/removerefuse to clobber a custom hook with different content (returns409)
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 | — | GET /project/{project}?a=commit |
Commit information. |
| commitdiff | — | GET /project/{project}?a=commitdiff |
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 |
Query parameter short names (CGI mapping)
p→ projecta→ actionf→ file_namefp→ file_parenth→ hashhp→ hash_parenthb→ hash_basehpb→ hash_parent_basepg→ pageo→ orders→ searchtextst→ searchtypesf→ snapshot_formatopt→ extra_optionssr→ search_use_regexpby_tag→ ctagds→ diff_stylepf→ project_filterupdates→ long-poll subscription flag (trueparks the request up to 30s; returns200 OKwith 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 OKwith 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); production deployments should restrict it
at the reverse proxy.
Hook management
The project summary page exposes an Update Hook row that installs / removes 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pygitweb-0.1.0.tar.gz.
File metadata
- Download URL: pygitweb-0.1.0.tar.gz
- Upload date:
- Size: 127.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e1f631ab2d6e02a189b1302c1f2e22afdde5e0835b768166d1b78433a00f5266
|
|
| MD5 |
cbb20dd22906ea19e348c56dbdcc38f7
|
|
| BLAKE2b-256 |
98630e445ef2e2d2f235437caa6b20d30cf8e201ed865d90d7c400aa4d78b4f3
|
File details
Details for the file pygitweb-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pygitweb-0.1.0-py3-none-any.whl
- Upload date:
- Size: 154.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4873f6373b917c194af91bb153805db6e5d102e01ed762cda832b5282a375660
|
|
| MD5 |
bdd26d718abf6f163f4065a19b53266b
|
|
| BLAKE2b-256 |
bb9c3299549ec80b865eb6c078b3846aabcf39befe6ba8c02376d5986e409d21
|