Documentation
¶
Overview ¶
Package core implements the ghfs FUSE filesystem as a tree of go-fuse nodes backed by a git blobless clone.
Hierarchy:
FS ← mountpoint root (e.g. ~/github.com)
User ← one dir per GitHub user/org; populated via REST Users/Orgs/Repos.List
Repository ← one dir per "<repo>" or "<repo>@<ref>". First access triggers
a blobless clone + in-memory tree build.
Dir ← subdirectories within the repo (tree-backed, no network)
File ← regular files; Open() streams the blob via the hydrator
Directory listings within repos are served entirely from the in-memory tree index. File reads trigger a cat-file fetch (first access) and then stream from the shared content-addressed blob cache.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ParseRepoAndRef ¶
ParseRepoAndRef splits a User-directory entry name of the form "<repo>" or "<repo>@<ref>" into its parts. Returns an error if the ref segment is present but empty, or if the repo name is empty.
<ref> may be a branch, tag, or commit SHA — ResolveRef in the gitstore layer handles which.
Types ¶
type Dir ¶
type Dir struct {
fs.Inode
Repo *reposrc.Repo
Ref string
Path string // path relative to repo root, "." for root
Logger *slog.Logger
}
Dir is any subdirectory within a repo. Resolves the tree on demand so Dir inodes created during the pre-clone Contents-API listing can still answer readdir/lookup once someone actually descends — that descent is the signal we use to trigger the (still-lazy) clone.
type FS ¶
type FS struct {
fs.Inode
// Client is used for the metadata listing (Users.Get,
// Organizations.List, Repositories.List). All content reads go
// through Repos.
Client *github.Client
// Repos materialises + tracks per-repo state (clones, trees,
// hydrators).
Repos *reposrc.Manager
Logger *slog.Logger
// contains filtered or unexported fields
}
FS is the filesystem root — one dir per GitHub user/org. It owns no git state; that lives in the reposrc.Manager.
func (*FS) Lookup ¶
func (f *FS) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno)
Lookup resolves a top-level owner/org name via Users.Get.
Do NOT pre-set out.SetEntryTimeout here. The rawBridge in go-fuse only applies NegativeTimeout on ENOENT if out.EntryTimeout() == 0 (bridge.go:362). If we pre-set it, every failed lookup leaks a zero-lifetime negative dentry and the kernel re-enters this handler on every probe — which is exactly what was making every fish_prompt render re-hit Users.Get for names like "HEAD". Successful returns still get entryTimeout applied by the bridge via setEntryOutTimeout (mount options set EntryTimeout=30min in main.go).
type File ¶
File is a regular git blob. Open() forces a hydrate into the shared cache and returns a FileHandle that reads from the cache file.
func (*File) Open ¶
Open hydrates the blob into the shared cache (if not already there) and opens the cache file for streaming reads. Binary-safe — the kernel-visible bytes are exactly the git blob's contents. If the inode was built from the pre-clone Contents-API listing, this is where the clone (and its hydrator) finally gets materialised.
type FileHandle ¶
type FileHandle struct {
// contains filtered or unexported fields
}
FileHandle streams from a backing cache file at an arbitrary offset.
type Repository ¶
type Repository struct {
fs.Inode
Owner string
Name string
Ref string // "" means default HEAD
Repo *reposrc.Repo
Client *github.Client // for the pre-clone Contents-API listing path
Logger *slog.Logger
// contains filtered or unexported fields
}
Repository is one directory: "<repo>" (ref="") or "<repo>@<ref>". Holds a reference to the shared reposrc.Repo (clone + hydrator) and defers tree building to first use.
func (*Repository) Getattr ¶
func (r *Repository) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno
Getattr reports the repo directory's FUSE attrs. mtime/ctime come from pushedAt when we've fetched it (seeded from the /user/repos listing at buildEntries time, or populated later by the GraphQL prefetch), else stay at zero rather than lying with time.Now() — the explicit 1970 is a signal to sorting tools ("unknown") whereas a fabricated "yesterday" would poison `find -mtime` and `ls -lt`.
atime is deliberately left unset: git doesn't track access, and setting it to anything (including now) would just noise up tools.
The attr timeout is userReaddirTTL (60s), not entryTimeout (30min): pushedAt only refreshes when User.Readdir re-paginates the owner's repos (bounded by userReaddirTTL) or the GraphQL metadata prefetch fires. Caching longer would leave `ls -lt` stuck on a pre-push mtime for up to half an hour after new work lands. Re-calling Getattr at 60s is cheap — just a mutex-guarded read of pushedAt.
func (*Repository) Readdir ¶
Readdir lists the repo root. If the repo has already been cloned, we serve from the in-memory tree; otherwise we fall back to the GitHub Contents API so shells that probe one level deeper than the user's cursor (fish tab-completion, eza --git, etc.) don't force a clone stampede across every sibling repo. Cloning is deferred to the first real traversal (Dir.Readdir, File.Open, Symlink.Readlink).
type Symlink ¶
type Symlink struct {
fs.Inode
Repo *reposrc.Repo
Ref string
Node model.BaseNode
Logger *slog.Logger
}
Symlink is a git symlink blob. We hydrate it once (they're small) to read the target.
type User ¶
type User struct {
fs.Inode
Login string
// OwnerType is "User" or "Organization" (from the Users.Get
// response). Empty if we couldn't determine it — Readdir falls
// back to the public Repositories.List in that case.
OwnerType string
// IsAuthedUser is true iff Login matches the token owner. Drives
// Readdir to use ListByAuthenticatedUser so private repos appear.
IsAuthedUser bool
Client *github.Client
Repos *reposrc.Manager
Logger *slog.Logger
// contains filtered or unexported fields
}
User is a GitHub user or organisation — one directory containing their repos.
func (*User) Lookup ¶
func (u *User) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno)
Lookup handles plain "<repo>" and "<repo>@<ref>" forms. Verifies the repo exists (via the Readdir-populated set, or a one-shot Repositories.Get) before returning a positive result. Returning ENOENT here lets the kernel negatively cache bogus probes (e.g. jj walking up looking for ".jj") so they don't cascade into clone attempts for non-existent repos.
func (*User) Readdir ¶
Readdir enumerates the owner's repos. Picks the right endpoint based on what we know about the owner:
- Authenticated user's own dir → ListByAuthenticatedUser (GET /user/repos) with Affiliation="owner,collaborator" so private repos + collab repos appear.
- Organization → ListByOrg (GET /orgs/{org}/repos) which returns everything the authed user can see in that org (including private).
- Any other user → Repositories.List (GET /users/{login}/repos) which returns public repos only — the GitHub API doesn't expose third-party users' private repos anyway.
Successful listings are cached on the User inode for userReaddirTTL so repeated `ls` / shell tab-completion don't re-paginate. Pre- populates child inodes to avoid subsequent Lookup calls and records known names so Lookup can answer ENOENT locally.