Git and libgit2

Git LFS Server URLs

Published 2025-01-06.
Time to read: 8 minutes.

This page is part of the git collection.

I have published 5 articles about the Git large file system (LFS). They are meant to be read in order.

  1. Git Large File System Overview
  2. Git LFS Client Installation
  3. Git LFS Server URLs
  4. Git LFS Filename Patterns & Tracking
  5. Git LFS Client Configuration & Commands
  6. Working With Git LFS
  7. Evaluation Procedure For Git LFS Servers

7 articles are still in process.

Instructions for typing along are given for Ubuntu and WSL/Ubuntu. If you have a Mac, most of this information should be helpful.

Default LFS Server URL

The default LFS server is assumed to be provided by the same organization as your git server. By default, the LFS remote is derived from the origin remote.

For example, if your git repository resides on GitHub, the default Git LFS server is also assumed to be provided by GitHub. If the value for your origin remote is git@github.com:foo/bar.git, then a value of https://github.com/foo/bar.git/info/lfs will be used for lfs.url.

The rest of this article, and the remainder of this series of articles about Git LFS, assumes that you want to use a different Git LFS server than the default.

Server Discovery

I enhanced the following quote with information about some of the git configuration variables involved and examples of the various flavors of the local protocol.

One of the Git LFS goals is to work with supporting Git remotes with as few required configuration properties as possible. Git LFS will attempt to use your Git remote to determine the LFS server. You can also configure a custom LFS server if your Git remote doesn't support one, or you just want to use a separate one.

Look for the Endpoint properties in git lfs env to see your current LFS servers, or examine the value of lfs.url and compare to the value of remote.origin.url.

Guessing the Server

By default, Git LFS will append /info/lfs or .git/info/lfs to the end of a Git remote URL to build the LFS server URL it will use. However, this is not the case with the local protocol, which comes in three flavors. If remote.origin.url specifies a syntax that is recognized as the local protocol, no git server is used. Instead, the git client does all the work. Similarly, if lfs.url specifies a syntax that is recognized as the local protocol, no Git LFS server is used, and again the git client does all the work.

Git Remote: /foo/bar
LFS Server: none; the git client directly accesses LFS content in the local directory /foo/bar/lfs


Git Remote: file:///node/bar
LFS Server: none; the git client directly accesses LFS content at the shared network directory file:///node/bar/lfs


Git Remote: file:////unc_dir/bar
LFS Server: none; the git client directly accesses LFS content in the Windows UNC path \\unc_dir\bar\lfs
. I do not know if git can use this variant of the local protocol on Macs or Linux machines.

Git Remote: https://git-server.com/foo/bar
LFS Server: https://git-server.com/foo/bar.git/info/lfs

Git Remote: https://git-server.com/foo/bar.git
LFS Server: https://git-server.com/foo/bar.git/info/lfs

Git Remote: git@git-server.com:foo/bar.git
LFS Server: https://git-server.com/foo/bar.git/info/lfs

Git Remote: ssh://git-server.com/foo/bar.git
LFS Server: https://git-server.com/foo/bar.git/info/lfs

You can, of course, override the guessed value of lfs.url. The next section details how.

.lfsconfig

You probably know about the client-side git configuration file called .gitignore, which informs the git client the files and directories that should not be considered for inclusion into the git repository.

Similarly, the Git LFS extension for the git command-line client looks for another file at the top of a git repository: .lfsconfig. Like .gitignore, .lfsconfig must be checked in. This means that Git LFS configuration information is automatically available when cloning a git repository.

.lfsconfig informs the git client what the access point of the Git LFS server is. If you want to point the git client at a different Git LFS server, just change the value for lfs.url stored in this file.

The man page for git-lfs-config describes the content of .lfsconfig:

The .lfsconfig file in a repository is read and interpreted in the same format as the file stored in .git/config. It allows a subset of keys to be used, including and limited to:

  • lfs.allowincompletepush
  • lfs.fetchexclude
  • lfs.fetchinclude
  • lfs.gitprotocol
  • lfs.locksverify
  • lfs.pushurl
  • lfs.skipdownloaderrors
  • lfs.url
  • lfs.\{*}.access
  • remote.{name}.lfsurl

The set of keys allowed in this file is restricted for security reasons.

Querying the Current Git and LFS Servers

As you probably know, you can view the git server for the current repository with:

Shell
mslinn@Bear my_repo $git config get remote.origin.url
git@github.com:mslinn/www.mslinn.com.git

You can view the current LFS server for each remote with:

Shell
mslinn@Bear my_repo $git lfs env | grep Endpoint
Endpoint=http://gojira:9876/mslinn/test (auth=none)

Custom Git LFS Server URL

Each Git LFS server is accessed via a unique URL, known to Git LFS as the lfs.url git configuration value. We will set that to a unique value for each Git LFS server that is evaluated.

The value should be stored in .lfsconfig. That file can also hold authentication information. The general form of the incantation to establish a value for lfs.url is:

Shell
mslinn@Bear my_repo $git config -f .lfsconfig lfs.url \
http://my_lfs_server/api/my_org/my_repo

Breaking down the above incantation:

  1. git config: This configuration value is local to the current git repository, so it is not a global setting.
  2. -f .lfsconfig: Write this configuration value to the file .lfsconfig, not the default file (.git/config).
  3. lfs.url: This is the name of a morsel of client-side Git LFS configuration data.
  4. This is the value of the aforementioned morsel of client-side Git LFS configuration data. When stored in .lfsconfig it looks as follows:
    .lfsconfig
    [lfs]
    url = "http://my_lfs_server/api/my_org/my_repo"
                  ───────┬───── ─┬─ ──┬─── ───┬───
                         │       │    │       └ The name of your project
                         │       │    └ Your organization name
                         │       └ See the next section
                         └ The host name and port of your LSF server

The above git config command also adds the following to .git/config:

.git/config fragment
[lfs "http://my_lfs_server/api/my_org/my_repo"]
locksverify = false

About the api/ Portion of the URL

The api/ portion of the URL depends on the Git LFS server. The Git LFS standard states that it must be present as shown. Some Git LFS servers, for example giftless, default to not complying with the standard. The local protocol, discussed next, does not utilize this portion of the URL.

Example

Create .lfsconfig as follows:

Shell
mslinn@Bear my_repo $git config -f .lfsconfig lfs.url http://gojira:9876/mslinn/myrepo

.lfsconfig should now look like this:

.lfsconfig
[lfs]
url = http://gojira:9876/mslinn/myrepo

Now commit the changes:

Shell
mslinn@Bear my_repo $git add .lfsconfig && \
git commit -m 'Set Git LFS server' && \
git push -u origin master

The next time this repository is checked out, the Git LFS server will be defined.

The File URI Schema And The Local Protocol

A git URL can have one of five types of schemas, which invoke a corresponding protocol. The Pro Git book describes most of the protocols. The complete list of protocols for git URLs are: git, http, https, local, and ssh. Git and Git LFS clients support all of these protocols.

The local protocol is available in several flavors. One of those flavors is the file: URI schema.

Most of the supported protocols (git, http, https, and ssh) are request-response protocols.

However, the local protocol is different from the other protocols in that it is passive. As you know, the only operations that can be performed with files are to read from them and write to them. The local protocol does not listen for changes to file systems, so there are no callbacks. This means that git client hooks are never triggered when using the local protocol. Instead, the git client does all the work.

The following incantation clones a local repository using the local protocol, which means the directory containing the repository being cloned is accessible from the computer. Note the three slashes in the URI:

Shell
mslinn@Bear ~ $git clone "file:///my_local_directory"

The following is equivalent to the above incantation:

Shell
mslinn@Bear ~ $git clone "file://localhost/my_local_directory"

You could also specify the same repository without the file:/// prefix:

Shell
mslinn@Bear ~ $git clone "my_local_directory"
Git operates slightly differently if you explicitly specify file:// at the beginning of the URL. If you just specify the path, Git tries to use hardlinks or directly copy the files it needs. If you specify file://, Git fires up the processes that it normally uses to transfer data over a network, which is generally much less efficient.

The main reason to specify the file:// prefix is if you want a clean copy of the repository with extraneous references or objects left out — generally after an import from another VCS or something similar (see Chapter 10.7 Git Internals - Maintenance and Data Recovery).

We’ll use the normal path here because doing so is almost always faster.

You can reference a mounted network path using the local protocol. The following example clones a repository residing on the local network node called gojira, with path git_path:

Shell
mslinn@Bear ~ $git clone "file://gojira/git_path" # Is this OK?
mslinn@Bear ~ $git clone "file:///gojira/git_path" # Is this OK?

UNC Paths

The syntax for a UNC path is similar to the syntax for the network path incantation immediately above. The syntax for a UNC path has four slashes.

The following example clones a repository residing on the machine with UNC address GOJIRA, in a public directory called git_directory:

Shell
mslinn@Bear ~ $git clone "file:////GOJIRA/git_directory"

Authentication

Two Approaches

There are two possible approaches for using Git LFS over SSH.

  1. The older form uses a program called git-lfs-authenticate, which provides authentication for an HTTP server, and then the data is uploaded over HTTP or HTTPS. This would be an unusual setup for a local server, and is suboptimal for a remote server.
  2. The Git LFS client v3.0, released 3 years ago on 2021-09-24, supports the Git LFS SSH protocol via the git-lfs-transfer program. Git-lfs-transfer has two implementations: the original Rust implementation that does not implement locking, and a Go implementation that does implement locking.

    If Git LFS detects an SSH remote, it will run the git-lfs-authenticate command. This allows supporting Git servers to give the Git LFS client alternative authentication, so the user does not have to set up a git credential helper.

The git-lfs-transfer command is generally the preferred Git LFS authentication option for the ssh protocol. Here are a few words about each of the two implementations:

  1. Scutiger published the original version of git-lfs-transfer on 2019-03-09. Within that repository, the scutiger-lfs subdirectory provides Git LFS-specific utilities. This repository is written in Rust, has been forked 7 times, starred 25 times and is watched by 6 GitHub users.
  2. Charmbracelet ported the Rust code written by scutiger to the Go language on 2022-12-09 and has since been forked 6 times. This project has been starred 62 times and is watched by 5 GitHub users.

Both versions are easy to install. An examination of the source code showed me that the Go version implements locking, while the Rust version does not. Neither version has good documentation, but the Go version has slightly worse (less) documentation than the Rust version.

Charmbracelet’s Go version has more than 250% more stars, even though it is only half as old. Although I think this is the version to use, the rest of this section shows you how to work with both versions.

Building git-lfs-transfer

Git-lfs-transfer runs on the Git LFS server, so it should be built on that machine. In the next two sections, I show how to build both the Go and the Rust versions. You can build both if you are so inclined. After they are built, you must choose one implementation.

Building Rust Version

You will need Rust 1.63 or later, Cargo, GNU Make, and Git. If you additionally have zlib, the 8-bit version of libpcre2, and libgit2 available, Scutiger will dynamically link against them. This is highly recommended for security reasons.
 – From Scutiger README

The build process is very easy:

Shell
mslinn@gojira ~ $cargo install \
  --git https://github.com/bk2204/scutiger.git \
  scutiger-lfs
Updating git repository `https://github.com/bk2204/scutiger.git`
  Installing scutiger-lfs v0.3.0 (https://github.com/bk2204/scutiger.git#614a4f01)
    Updating crates.io index
     Locking 98 packages to latest compatible versions
      Adding bytes v0.4.12 (available: v1.9.0)
      Adding clap v2.34.0 (available: v4.5.23)
      Adding digest v0.9.0 (available: v0.10.7)
      Adding git2 v0.16.1 (available: v0.19.0)
      Adding sha2 v0.9.9 (available: v0.10.8)
  Downloaded autocfg v1.4.0
  Downloaded icu_provider_macros v1.5.0
  Downloaded cpufeatures v0.2.16
  Downloaded zerofrom v0.1.5
  Downloaded write16 v1.0.0
  Downloaded errno v0.3.10
  Downloaded fastrand v2.3.0
  Downloaded displaydoc v0.2.5
  Downloaded idna_adapter v1.2.0
  Downloaded zerovec-derive v0.10.3
  Downloaded utf8_iter v1.0.4
  Downloaded synstructure v0.13.1
  Downloaded yoke-derive v0.7.5
  Downloaded version_check v0.9.5
  Downloaded zerofrom-derive v0.1.5
  Downloaded writeable v0.5.5
  Downloaded pkg-config v0.3.31
  Downloaded jobserver v0.1.32
  Downloaded tinystr v0.7.6
  Downloaded yoke v0.7.5
  Downloaded quote v1.0.38
  Downloaded once_cell v1.20.2
  Downloaded litemap v0.7.4
  Downloaded icu_normalizer v1.5.0
  Downloaded unicode-ident v1.0.14
  Downloaded tempfile v3.14.0
  Downloaded icu_provider v1.5.0
  Downloaded bitflags v2.6.0
  Downloaded url v2.5.4
  Downloaded serde v1.0.217
  Downloaded icu_collections v1.5.0
  Downloaded idna v1.0.3
  Downloaded zerovec v0.10.4
  Downloaded icu_properties v1.5.1
  Downloaded icu_normalizer_data v1.5.0
  Downloaded num-traits v0.2.19
  Downloaded icu_properties_data v1.5.0
  Downloaded vcpkg v0.2.15
  Downloaded unicode-width v0.1.14
  Downloaded syn v2.0.93
  Downloaded chrono v0.4.39
  Downloaded icu_locid_transform_data v1.5.0
  Downloaded proc-macro2 v1.0.92
  Downloaded log v0.4.22
  Downloaded icu_locid v1.5.0
  Downloaded rustix v0.38.42
  Downloaded cc v1.2.6
  Downloaded icu_locid_transform v1.5.0
  Downloaded iana-time-zone v0.1.61
  Downloaded stable_deref_trait v1.2.0
  Downloaded utf16_iter v1.0.5
  Downloaded libc v0.2.169
  Downloaded libz-sys v1.1.20
  Downloaded linux-raw-sys v0.4.14
  Downloaded 54 crates (6.7 MB) in 0.28s (largest was `linux-raw-sys` at 1.8 MB)
   Compiling proc-macro2 v1.0.92
   Compiling unicode-ident v1.0.14
   Compiling libc v0.2.169
   Compiling stable_deref_trait v1.2.0
   Compiling writeable v0.5.5
   Compiling litemap v0.7.4
   Compiling shlex v1.3.0
   Compiling pkg-config v0.3.31
   Compiling icu_locid_transform_data v1.5.0
   Compiling icu_properties_data v1.5.0
   Compiling vcpkg v0.2.15
   Compiling version_check v0.9.5
   Compiling typenum v1.17.0
   Compiling smallvec v1.13.2
   Compiling write16 v1.0.0
   Compiling utf16_iter v1.0.5
   Compiling utf8_iter v1.0.4
   Compiling icu_normalizer_data v1.5.0
   Compiling autocfg v1.4.0
   Compiling percent-encoding v2.3.1
   Compiling bitflags v1.3.2
   Compiling rustix v0.38.42
   Compiling bitflags v2.6.0
   Compiling log v0.4.22
   Compiling unicode-width v0.1.14
   Compiling cfg-if v1.0.0
   Compiling linux-raw-sys v0.4.14
   Compiling byteorder v1.5.0
   Compiling fastrand v2.3.0
   Compiling cpufeatures v0.2.16
   Compiling opaque-debug v0.3.1
   Compiling form_urlencoded v1.2.1
   Compiling iana-time-zone v0.1.61
   Compiling once_cell v1.20.2
   Compiling hex v0.4.3
   Compiling generic-array v0.14.7
   Compiling textwrap v0.11.0
   Compiling num-traits v0.2.19
   Compiling clap v2.34.0
   Compiling quote v1.0.38
   Compiling syn v2.0.93
   Compiling chrono v0.4.39
   Compiling jobserver v0.1.32
   Compiling iovec v0.1.4
   Compiling passwd v0.0.1
   Compiling bytes v0.4.12
   Compiling cc v1.2.6
   Compiling block-buffer v0.9.0
   Compiling digest v0.9.0
   Compiling sha2 v0.9.9
   Compiling tempfile v3.14.0
   Compiling libz-sys v1.1.20
   Compiling libgit2-sys v0.14.2+1.5.1
   Compiling synstructure v0.13.1
   Compiling zerofrom-derive v0.1.5
   Compiling yoke-derive v0.7.5
   Compiling zerovec-derive v0.10.3
   Compiling displaydoc v0.2.5
   Compiling icu_provider_macros v1.5.0
   Compiling zerofrom v0.1.5
   Compiling yoke v0.7.5
   Compiling zerovec v0.10.4
   Compiling tinystr v0.7.6
   Compiling icu_collections v1.5.0
   Compiling icu_locid v1.5.0
   Compiling icu_provider v1.5.0
   Compiling icu_locid_transform v1.5.0
   Compiling icu_properties v1.5.1
   Compiling icu_normalizer v1.5.0
   Compiling idna_adapter v1.2.0
   Compiling idna v1.0.3
   Compiling url v2.5.4
   Compiling git2 v0.16.1
   Compiling scutiger-core v0.3.0 (/home/mslinn/.cargo/git/checkouts/scutiger-b9c2aae4f2fdd878/614a4f0/scutiger-core)
   Compiling scutiger-lfs v0.3.0 (/home/mslinn/.cargo/git/checkouts/scutiger-b9c2aae4f2fdd878/614a4f0/scutiger-lfs)
    Finished `release` profile [optimized] target(s) in 8.55s
  Installing /home/mslinn/.cargo/bin/git-lfs-transfer
   Installed package `scutiger-lfs v0.3.0 (https://github.com/bk2204/scutiger.git#614a4f01)` (executable `git-lfs-transfer`)

The build process stores the built code in ~/.cargo/bin/:

Shell
mslinn@gojira scutiger $ls ~/.cargo/bin
git-lfs-transfer*

The help information is incomplete:

Shell
mslinn@gojira scutiger $git-lfs-transfer -h
git-lfs-transfer
Implement the remote side of a Git LFS SSH transfer
USAGE: git-lfs-transfer <path> <operation>
FLAGS: -h, --help Prints help information -V, --version Prints version information
ARGS: <path> <operation>

Building Go Version

Shell
mslinn@gojira ~ $go install \
  github.com/charmbracelet/git-lfs-transfer@latest
go: downloading github.com/charmbracelet/git-lfs-transfer v0.1.0
go: downloading github.com/git-lfs/git-lfs/v3 v3.3.0
go: downloading github.com/git-lfs/pktline v0.0.0-20210330133718-06e9096e2825
go: downloading golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
go: downloading github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086
go: downloading github.com/git-lfs/wildmatch/v2 v2.0.1
go: downloading github.com/git-lfs/gitobj/v2 v2.1.1
go: downloading github.com/leonelquinteros/gotext v1.5.0
go: downloading github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17

The go install command places executables in ~/go/bin/

Shell
mslinn@gojira ~ $ls ~/go/bin
git-lfs-transfer*

The help information for the Go version is even less helpful than the Rust version’s help information:

Shell
mslinn@gojira ~ $~/go/bin/git-lfs-transfer
Git LFS SSH transfer agent
Usage: git-lfs-transfer PATH OPERATION
expected 2 arguments, got 0

Installing git-lfs-transfer

The git-lfs-transfer must be installed in the PATH of the Git LFS server. You can discover the value of the PATH as follows. This incantation can be run from any machine, be it client or server:

Shell
mslinn@Bear ~ $ssh mslinn@gojira 'echo $PATH'
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

I prefer to use a symbolic link from /usr/local/bin/, so if git-lfs-transfer is ever updated, the link will continue to work.

Installing Rust Version

Shell
mslinn@gojira scutiger $sudo ln -s \
  "$HOME/go/bin/git-lfs-transfer" \
  /usr/local/bin/git-lfs-transfer
mslinn@gojira scutiger $which git-lfs-transfer /usr/local/bin/git-lfs-transfer
mslinn@gojira scutiger $ssh mslinn@gojira which git-lfs-transfer /usr/local/bin/git-lfs-transfer

Installing Go Version

Shell
mslinn@gojira scutiger $sudo ln -s \
"$HOME/.cargo/bin/git-lfs-transfer" \
/usr/local/bin/git-lfs-transfer
mslinn@gojira scutiger $which git-lfs-transfer /usr/local/bin/git-lfs-transfer
mslinn@gojira scutiger $ssh mslinn@gojira which git-lfs-transfer /usr/local/bin/git-lfs-transfer

Testing git-lfs-transfer

Once installed, the git-lfs-transfer program is automatically run on the server as required. Users do not need to know it exists. The rest of this section is only provided for those who might be curious about further details.

When the ssh protocol is used, git clients with the Git LFS extension run the following command to authenticate:

Shell
mslinn@gojira scutiger $ssh [{user}@]{server} \
  git-lfs-transfer {path} {operation}

The user, server, and path properties are taken from the SSH remote, stored in the user’s copy of the repository in .git/config. The {operation} can either be download or upload. If a git client is used, the SSH command can be tweaked with the GIT_SSH or GIT_SSH_COMMAND environment variables.

Let's try it now. This will give us confidence that git-lfs-transfer is properly set up, so Git LFS will be able to work with it without problems. The following incantation can be invoked from any server, be it client or server. I provided the fully qualified path to the bare git repository on the server (/mnt/_/work/git/eval_bare_repo). The git client with the Git LFS extension must also provide this path.

Shell
mslinn@Bear ~ $ssh mslinn@gojira git-lfs-transfer \
  /mnt/_/work/git/eval_bare_repo download
000eversion=1
000clocking
0000^C

Using git-lfs-transfer

I typed the following from the top-level directory within a git repo:

Shell
mslinn@Bear eval_bare_repo $git config -f .lfsconfig lfs.url \
  ssh://mslinn@gojira/mnt/_/work/git/eval_bare_repo

Then .lfsconfig will have at least the following:

.lfsconfig
[lfs]
        url = ssh://mslinn@gojira/mnt/_/work/git/eval_bare_repo

Which means the Git command-line client will issue a command something like the following when fetching an LFS file:

Shell
$ ssh mslinn@gojira git-lfs-transfer /mnt/_/work/git/eval_bare_repo download

When I type the above incantation, the response is:

Shell
000eversion=1
000clocking
0000

And then the program appears to pause, awaiting input.

I have published 5 articles about the Git large file system (LFS). They are meant to be read in order.

  1. Git Large File System Overview
  2. Git LFS Client Installation
  3. Git LFS Server URLs
  4. Git LFS Filename Patterns & Tracking
  5. Git LFS Client Configuration & Commands
  6. Working With Git LFS
  7. Evaluation Procedure For Git LFS Servers

7 articles are still in process.

Instructions for typing along are given for Ubuntu and WSL/Ubuntu. If you have a Mac, most of this information should be helpful.

* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.