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

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

Route Git Through a Controlled External Transport with git-remote-ext

You will finish with a working pattern for sending Git's smart-transport traffic through an external command, such as ssh, socat or a site-specific wrapper. You will also know what Git substitutes into the command, which arguments are consumed by the helper, and where debugging output comes from.

Allow about twenty minutes. You need Git 2.43.0 or a nearby release, a repository URL that you are authorised to access, and the external program named in the remote URL. 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 because the helper's contract belongs to your Git version:

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

The helper is normally reached indirectly. Git sees a URL beginning with ext::, starts the command after that prefix, and connects the command's standard input and output 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 an SSH connection with an explicit key, add a remote using the long service substitution %S. Git replaces it with 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 verified. The SSH process receives 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 substitutions to this URL: git-remote-ext splits the command and arguments on unescaped spaces; it does not describe 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. The useful result is exit status zero and a remote-tracking update. If the remote is push-only or the path is wrong, Git and SSH will report that without changing the remote configuration.

3. Use the percent substitutions deliberately

The substitutions 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. This is usually the convenient form for SSH.
  • %G/repo, only when it starts an argument, asks the helper to send a Git protocol service request and uses /repo as its repository field.
  • %Vhost, only when it starts an argument, supplies a virtual host field for that Git protocol request.

A literal space inside one command argument is written as % , a percent sign followed by a space. For example, the following describes a helper argument containing 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 recognised only at the start of one. Also do not pass the repository path twice: with %G/repo, the path is sent in the protocol request rather than passed 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

This is a configuration example, not a promise that your host has that socket or that socat is installed. Test a real tunnel only during an approved maintenance window. A tunnel can expose repository data to a different security boundary, and a service disruption is possible if you replace an existing remote without a rollback plan.

5. Inspect the service environment with a wrapper

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

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

Keep a wrapper small and explicit. It should validate the service name, select a fixed trusted destination, and 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"

The wrapper above is illustrative: its final command must match the remote system's interface. Install it only in a location you control, make it executable, and test it with a disposable repository before assigning it to a production remote. If the wrapper rejects the service, Git will return a non-zero status and no fetch or push should be treated as successful.

6. Diagnose a failed connection

First inspect 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'

The last command prints effective SSH configuration. It 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:

$ 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 output before sharing it. The variable reports reads and writes; it does not repair a bad SSH key, missing socket or rejected service.

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

$ git remote remove tunnel
$ git remote -v

Removal changes local repository configuration but does not delete the remote repository. Before removing an important remote, save its URL with git remote get-url tunnel. There is no elevated-privilege step in this recovery.

Done means

  • You confirmed the installed Git and git-man versions.
  • Your ext:: URL names a trusted external command and has been checked with git remote get-url.
  • You chose %S, %s, %G and %V according to the remote protocol rather than guessing.
  • A fetch, push or archive operation has been tested against an authorised endpoint.
  • Any wrapper validates GIT_EXT_SERVICE_NOPREFIX before opening a tunnel.
  • Debugging output and SSH configuration details will be treated as sensitive operational data.