Skip to content
forked from agzam/remoto.el
 
 

Repository files navigation

El Remoto

El Remoto logo

https://github.com/agzam/remoto.el/actions/workflows/run-tests.yml/badge.svg

El Remoto - browse any GitHub repository in Emacs just like any local - without cloning.

What

Wouldn’t it be nice to be able to C-x C-f and just browse any GitHub repo?

./test/demo.gif

remoto.el registers a virtual filesystem via file-name-handler-alist that translates standard Emacs file operations into GitHub API calls via the ghub library. The result: find-file, dired, tab-completion, dired-subtree - all standard Emacs file tooling works against a remote GitHub repo. Read-only.

It was demoed at Emacs ATX Meetup of July 2026. Here’s the video. Link to the complete video of that meetup (~2 hrs) is in the description.

Why

Sometimes you just want to look at code. Check a function signature, read a README, browse a project structure. Cloning a repo for that is overkill - it takes disk space, creates another directory to manage, and breaks your flow.

Where

For now it works only with GitHub repos. Future plans include support for other forges - GitLab, Codeberg, etc.

How

use-package + straight

Loading the package defines things and changes nothing. global-remoto-mode is the switch: it installs the file handler, the URL rewriting in find-file and dired, and the completion metadata, and removes them all when turned off. Nothing else is needed:

(use-package remoto
  :straight (:host github :repo "agzam/remoto.el"))

The mode turns itself on at first use, whichever way that comes: M-x remoto-browse, an Embark open action, or a /github: or /gh: path typed at C-x C-f, C-x d or any other file prompt. For the last one the package autoloads register a small handler for those two prefixes, the way TRAMP loads on the first remote path; it hands over to the real handler and steps aside. Each of these says so once in the echo area. To have the mode on from the start, and skip that message, put (global-remoto-mode 1) in your config.

The package is not on MELPA yet. Maybe upvote the submission PR, who knows, perhaps it gets accepted faster.

Usage

remoto-browse

The main entry point. Accepts any GitHub URL format:

M-x remoto-browse RET https://github.com/torvalds/linux RET
M-x remoto-browse RET git@github.com:torvalds/linux.git RET
M-x remoto-browse RET torvalds/linux RET

C-x d / C-x C-f

GitHub URLs are detected automatically in dired and find-file:

C-x C-f /github:torvalds/linux RET

;; shorthand also works
C-x C-f /gh:torvalds/linux RET

;; complete urls work too
C-x C-f https://github.com/torvalds/linux/blob/master/README RET

find-file completion

The /github: prefix (/gh: shorthand works too) enables multi-level completion directly in find-file (C-x C-f). Three delimiters after owner/repo control the mode: / for files, @ for branches/tags, # for issues/PRs.

/github:              -> your user + orgs (when authenticated)
/github:tor           -> users/orgs matching "tor"
/github:torvalds/     -> repos (with descriptions)
/github:torvalds/linux/        -> files on default branch (with last commit messages)
/github:torvalds/linux@        -> branches + tags (grouped)
/github:torvalds/linux@master: -> file-level completion
/github:torvalds/linux#        -> issues + PRs

Each level shows annotations: repo descriptions, issue/PR titles and state, file commit messages, user/org type. Works with vanilla completion, Vertico, Corfu, etc. While a GitHub request is in flight, a sits between the prompt and the path being typed, and once the path is chosen and opens, the same mark sits in the echo area; remoto-browse shows the same two, and remoto-show-fetch-indicator turns both off.

Hitting RET on /github:owner/repo opens the default branch. /github:owner/repo#42 opens an issue/PR display buffer.

Canonical paths

The virtual filesystem uses paths of the form:

/github:OWNER/REPO@REF:/PATH

REF is optional - omitting it uses the repo’s default branch.

Issue/PR display

Opening /github:owner/repo#42 shows a dedicated buffer with the issue or PR content. For PRs: diff stats, branch info, merge status, and review summary. For both: body + comments.

Keybindings in the display buffer: b (open in browser), g (refresh).

Commands

CommandDescription
global-remoto-modeTurn the file handler, URL rewriting and completion on or off
remoto-browsePrompt for a repo, open dired at root
remoto-refreshClear tree cache for current repo, re-fetch
remoto-copy-urlCopy github.com URL for current file/line to kill ring

Embark integration

Optional Embark integration lives in remoto-embark.el. Embark is not a hard dependency; when it is installed the integration activates automatically, with nothing to configure. With it active, embark-act offers forge-agnostic actions on remoto targets in remoto file buffers, dired, and /github: minibuffer completion (including embark-collect).

Each target type has its own keymap of actions that reuse remoto’s forge-agnostic URL layer (copy or browse the web URL, clone, blame, permalink, open an issue/PR, and so on):

TargetExampleKeymap
remoto-owner/github:torvaldsremoto-embark-owner-map
remoto-repo/github:torvalds/linux:/remoto-embark-repo-map
remoto-branch/github:torvalds/linux@master:/remoto-embark-branch-map
remoto-issue/github:torvalds/linux#42remoto-embark-issue-map
remoto-dir/github:torvalds/linux@master:/fsremoto-embark-dir-map
remoto-file/github:torvalds/linux@master:/READMEremoto-embark-file-map

Run embark-act on a target to see its actions and keys. Cloning honors remoto-clone-url-type and opens the clone’s Magit status buffer when it finishes (dired without Magit), and Embark’s built-in url target gains R to open a forge URL at point as a remoto path.

How does it work

  1. On first access to any file in a repo, the full directory tree is fetched via the Git Trees API (one HTTP call).
  2. Directory listings, file-exists-p, completions - all served from the cached tree in memory.
  3. File contents are fetched on demand when you actually open a file.
  4. Content is cached by SHA, so re-opening the same file is instant.
  5. Authentication, SSO, private repos - all handled transparently by ghub.

FAQ

Why this isn’t a TRAMP backend?

  • TRAMP fundamentally is a shell-over-transport abstraction. Every TRAMP backend assumes a remote shell on the other end, remoto.el has no remote shell.
  • TRAMP’s backend API is large. You’d need to implement or stub dozens of operations, many of which assume concepts that don’t map to a REST API (process execution, file ownership, symlink resolution, timestamps).
  • TRAMP’s connection management (open/close/reuse) is built around persistent sessions. The GitHub API is stateless HTTP - there’s no connection to manage.
  • TRAMP’s caching layer is path-based and per-connection. remoto’s tree cache (one API call fetches the entire repo tree, then everything is served from memory) is a fundamentally different - and much more efficient - model for a read-only tree-structured API.
  • file-name-handler-alist IS the same mechanism TRAMP uses. Registering there directly gives you the same integration (dired, find-file, completions) without the framework overhead.

For a read-only tree browser backed by a REST API, a direct file-name-handler is the simpler, more natural fit

Why not just use plain HTTP?

Git’s own protocols let you talk to a remote repo without cloning it, so why not use that, right?

  • Tree listing. There’s no raw HTTP URL that gives you a directory listing. raw.githubblabla.com can’t do directory indexes. You’d have to shell out to git ls-tree ... etc. over the ssh, which means essentially implementing a partial git client.
  • Branch listing and repo search - no git protocol equivalent for those - need the API
  • Current approach fetches the entire tree in one API call. Doing the same over git pack protocol means negotiating a fetch, receiving packfile data and parsing it. Much heavier, much more code.

We can only imagine a world where git’s transport layer gives you a browsable filesystem interface. It doesn’t - git’s protocols are optimized for syncing object graphs, not random-access file browsing.

Limitations

  • Read-only. No commits, no pushes, no file modifications. You still can copy them out.
  • No git operations (log, blame, diff).
  • Timestamps in dired are synthetic (the tree API has no timestamps).
  • Very large repos (100k+ files) use slower per-directory fetching.
  • Rate limit: 5,000 requests/hour (normal browsing stays well within this).
  • Per-file commit annotations make up to 20 API calls on first directory listing (cached after).

Changelog

See changelog.org for a detailed list of changes and version history.

License

Copyright © 2026 Ag Ibragimov

GPL-3.0-or-later. See LICENSE.

About

Browse GitHub repos without cloning

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages