Skip to content

namazso/browsegit

Repository files navigation

browsegit

A static single-page app that browses any git repository served as plain static files (git's "dumb" HTTP protocol) — entirely in the browser. No server-side git, no preprocessing, no API: point it at a URL where a repo's files are reachable and it fetches refs, trees, blobs, and packfiles on demand.

The UI loosely follows GitHub's "Code" tab:

  • Tree browsing with breadcrumbs, branch/tag selector, and README rendering below directory listings
  • File view with shiki syntax highlighting, line numbers, and shareable #L10-L20 line-range anchors; images render inline, binaries offer download
  • GitHub-flavored markdown (tables, task lists, raw HTML sanitized) with relative links and images resolved from inside the repository
  • Commit history with pagination and per-file history (the "History" button on any file or directory)
  • Commit view with per-file diffs against the first parent
  • Find file (t) with fuzzy matching
  • Code button with the clone URL (the served files double as a git clone remote) and a ZIP download of the tree, built in the browser
  • Branches and tags pages, dark mode, recent-repos landing page

How it works

isomorphic-git can't fetch over dumb HTTP (it only speaks the smart protocol), but all of its read APIs go through a pluggable fs. browsegit implements a read-only fs (src/git/httpFs.ts) whose readFile is an HTTP fetch against the repo URL:

  • loose objects, HEAD, packed-refs map 1:1 to URLs
  • readdir('objects/pack') is synthesized from objects/info/packs
  • readdir('refs/…') is synthesized from info/refs
  • 404s become ENOENT, which isomorphic-git already handles as "fall back" (loose object → packfile, loose ref → packed-refs)

Everything else — commit walking, tree diffs, path-filtered logs — is stock isomorphic-git running against that fs, with responses cached per session (git objects are content-addressed, so object caches never go stale).

Serving a repository

The repo must be reachable from the page — same origin, or served with CORS headers (Access-Control-Allow-Origin: * is enough; only GET is used) — and set up for dumb-HTTP access:

# one-off
git -C /path/to/repo.git update-server-info

# or automatically on every push (bare repos)
mv hooks/post-update.sample hooks/post-update

That writes info/refs and objects/info/packs, which is how browsegit discovers refs and packfiles (directory listings don't exist over dumb HTTP). Any static file server works: nginx, GitHub Pages, S3, python -m http.server, npx http-server --cors, …

Then open the app with the repo URL in the query string:

https://browsegit.namazso.eu/?repo=https://example.com/myrepo.git
https://browsegit.namazso.eu/?repo=/repos/myrepo.git        (same origin)

Development

npm install
npm run fixtures   # generate sample repos under public/fixtures/
npm run dev        # then open /?repo=/fixtures/test.git
  • npm test — unit tests plus an end-to-end suite that runs the whole git layer against real repositories created with the git CLI (fetch is stubbed to serve their files from disk, byte-for-byte what a static server returns)
  • npm run typecheck — app and test configs
  • npm run build / npm run preview — production build (hash routing and a relative base, so it deploys to any static host or subdirectory)

Deploying

.github/workflows/deploy.yml builds the site (tests included) and publishes it to GitHub Pages on every push to the default branch. Enable it once under repo Settings → Pages by setting the source to "GitHub Actions" (the workflow also attempts to enable this itself). The deployed site ships the sample repos, so it works immediately at https://<user>.github.io/<repo>/?repo=fixtures/test.git — note the relative repo= value: on a project page a leading / would point outside the site.

Stack: React 19 + Vite + TypeScript, Tailwind CSS 4, TanStack Query, react-router (hash routing), isomorphic-git, shiki, react-markdown + remark-gfm + rehype-sanitize, jsdiff + react-diff-view, Primer octicons.

Limitations

Inherent to the dumb protocol or to keeping the client honest:

  • git update-server-info is required on the served repo (see above); without it there is no way to enumerate refs or find packfiles.
  • Whole packfiles are downloaded the first time a packed object is read (isomorphic-git parses packs in memory). Fine for small-to-medium repos; a future optimization could use HTTP Range reads via the pack .idx.
  • No last-commit-per-file column, blame, code search, or language statistics — each would require downloading large fractions of history.
  • Mode-only changes (e.g. chmod) don't show a content diff; renames are displayed as delete + add; submodules appear as inert entries.
  • Per-file history doesn't apply git's history simplification, so around merges it can list a few commits that didn't actually touch the file (isomorphic-git's path filter tracks the file linearly across the walk).

About

Static single-page git repository browser

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Contributors