Home / Alt manpages / git-remote-ext(1)

  • git-remote-ext(1)
  • User command
  • linux

Route Git Traffic Through git-remote-ext

When SSH and HTTPS both fail you, git-remote-ext lets Git speak through any command you can pipe bytes through.

This walks through the URL syntax, the substitutions Git fills in, and where the debugging output comes from.

Allow about twenty minutes. You need Git 2.43.0 or a nearby release, a repository URL you are authorised to access, and the external program named in that URL already installed. The examples are unprivileged: nothing here needs sudo, and the checks do not alter a server.

1. Confirm the installed helper

This guide follows the installed git-remote-ext(1) from the Ubuntu git-man package, version 1:2.43.0-1ubuntu7.3. Check your own installation before copying a command: the helper's contract belongs to your Git version, not this page.

$ git --version
git version 2.43.0
$ dpkg-query -W -f='${Package} ${Version}\n' git-man
git-man 1:2.43.0-1ubuntu7.3
$ git remote-ext --help
usage: git remote-ext <remote> <url>

You reach the helper indirectly. Git sees a URL beginning with ext::, starts the command after that prefix, and wires that command's standard input and output straight to the Git service.

Checkpoint

Your version is known, and the external program you intend to run is installed and trusted.

2. Start with an SSH transport

For SSH with an explicit key, add a remote using the long service substitution %S. Git swaps it for the service name it needs, such as git-upload-pack for a fetch:

$ git remote add origin "ext::ssh -i /home/you/.ssh/repository_key [email protected] %S /srv/git/project.git"
$ git remote get-url origin
ext::ssh -i /home/you/.ssh/repository_key [email protected] %S /srv/git/project.git

Replace the key path, account, host and repository path with values you have actually verified. The SSH process gets Git's protocol stream on standard input and must return the remote service's stream on standard output. Do not add shell syntax such as |, && or command substitution to this URL: git-remote-ext splits the command and its arguments on unescaped spaces. It is not describing a shell script.

Fetch only after checking the stored URL. This contacts the remote and may update local remote-tracking refs, so treat it as a normal network operation:

$ git fetch origin
From example.org:/srv/git/project
 * [new branch]      main       -> origin/main

Exact progress text varies. What matters is exit status zero and an updated remote-tracking branch. If the remote is push-only or the path is wrong, Git and SSH report that without touching your remote configuration.

3. Use the percent substitutions deliberately

These are part of the remote URL syntax, not shell variables:

  • %% becomes one literal percent sign.
  • %s becomes the short service name. Such as upload-pack, receive-pack or upload-archive.
  • %S becomes the long name. Such as git-upload-pack, usually the convenient form for SSH.
  • %G/repo, only at the start of an argument, requests a Git protocol service. It sends /repo as the repository field.
  • %Vhost, only at the start of an argument, supplies a virtual host field for that Git protocol request.

A literal space inside one command argument is written % , a percent sign followed by a space. This describes a helper argument that contains spaces:

ext::git-server-alias foo %G/repo% with% spaces %Vvirtual-host

Do not put %G or %V in the middle of an argument; they are only recognised at the start of one. And do not pass the repository path twice: with %G/repo, the path travels in the protocol request, not as an ordinary argument.

4. Connect a Git protocol service through a tunnel

Use %G when the external command is a byte-preserving tunnel to a Git protocol endpoint. The manual's representative shape uses socat and an abstract Unix socket:

$ git remote add tunnel "ext::socat -t3600 - ABSTRACT-CONNECT:/git-server %G/somerepo"
$ git remote get-url tunnel
ext::socat -t3600 - ABSTRACT-CONNECT:/git-server %G/somerepo

That is a configuration example, not a promise your host has that socket or even socat installed. Test a real tunnel only during an approved maintenance window. A tunnel can move repository data across a different security boundary than you think, and swapping an existing remote without a rollback plan can cause a real outage.

5. Inspect the service environment with a wrapper

If a custom helper needs to choose between fetch, push and archive, it can read two environment variables Git passes to the external command:

  • GIT_EXT_SERVICE holds the long name, for example git-upload-pack.
  • GIT_EXT_SERVICE_NOPREFIX holds the name without the git- prefix, for example upload-pack.

Keep a wrapper small and explicit: validate the service name, pick a fixed trusted destination, then replace itself with the tunnel or server connector. Never concatenate an untrusted repository name into shell source.

#!/bin/sh
set -eu
case "${GIT_EXT_SERVICE_NOPREFIX-}" in
  upload-pack|receive-pack|upload-archive) ;;
  *) echo "unsupported Git service" >&2; exit 64 ;;
esac
exec /usr/bin/ssh [email protected] "/usr/local/bin/git-$GIT_EXT_SERVICE_NOPREFIX"

That wrapper is illustrative: its final command must match your remote system's actual interface. Install it only where you control it, make it executable, and test with a disposable repository before pointing a production remote at it. If the wrapper rejects the service, Git returns a non-zero status, and you should treat any fetch or push as failed.

6. Diagnose a failed connection

Start by inspecting the URL and the external command without changing anything:

$ git remote get-url origin
$ command -v ssh
/usr/bin/ssh
$ ssh -G example.org | sed -n '1,12p'

That last command prints effective SSH configuration, which may include sensitive host details, so do not paste it into a public issue without review.

For transport-level reads and writes, set GIT_TRANSLOOP_DEBUG for one command only:

$ GIT_TRANSLOOP_DEBUG=1 git ls-remote origin
<debug output varies by Git version>

This can expose protocol details and paths in your terminal or logs. Use it briefly and redact before sharing. It reports reads and writes; it does not fix a bad SSH key, a missing socket or a rejected service.

If the remote entry itself is wrong, remove only that entry and recreate it with the corrected URL:

$ git remote remove tunnel
$ git remote -v

Recovery

Removal changes local repository configuration only, it does not delete the remote repository. Save the URL first with git remote get-url tunnel before removing an important remote. There is no elevated-privilege step in this recovery.

Done means

  • Installed Git and git-man versions are confirmed. No guessing at helper behaviour across versions.
  • Your ext:: URL names a trusted command. Checked with git remote get-url.
  • %S, %s, %G and %V were chosen for the protocol, not picked at random.
  • A fetch, push or archive was tested against an authorised endpoint. Not just added and left.
  • Any wrapper validates GIT_EXT_SERVICE_NOPREFIX before it opens a tunnel.
  • Debugging output and SSH configuration are treated as sensitive. Not pasted into a public issue.