core

package
v0.0.0-...-319015a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2026 License: MIT, Apache-2.0 Imports: 21 Imported by: 0

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

func ParseRepoAndRef(name string) (repo, ref string, err error)

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.

func (*Dir) Getattr

func (d *Dir) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno

func (*Dir) Lookup

func (d *Dir) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno)

func (*Dir) Readdir

func (d *Dir) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno)

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) Getattr

func (f *FS) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno

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).

func (*FS) Readdir

func (f *FS) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno)

Readdir lists the authenticated user plus their orgs. Kept identical in shape to the pre-refactor behaviour.

type File

type File struct {
	fs.Inode

	Repo *reposrc.Repo
	Ref  string
	Node model.BaseNode

	Logger *slog.Logger
}

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) Getattr

func (f *File) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno

func (*File) Open

func (f *File) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno)

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.

func (*FileHandle) Read

func (fh *FileHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)

func (*FileHandle) Release

func (fh *FileHandle) Release(ctx context.Context) syscall.Errno

Release closes the backing file when the kernel releases the handle.

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) Lookup

func (r *Repository) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno)

func (*Repository) Readdir

func (r *Repository) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno)

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 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.

func (*Symlink) Getattr

func (s *Symlink) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno
func (s *Symlink) Readlink(ctx context.Context) ([]byte, syscall.Errno)

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) Getattr

func (u *User) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno

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

func (u *User) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL