Published 2025-01-06.
Time to read: 8 minutes.
git
collection.
I have published 5 articles about the Git large file system (LFS). They are meant to be read in order.
- Git Large File System Overview
- Git LFS Client Installation
- Git LFS Server URLs
- Git LFS Filename Patterns & Tracking
- Git LFS Client Configuration & Commands
- Working With Git LFS
- 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/
,
then a value of https://github.com/
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/
Git Remote: file:///node/bar
LFS Server: none; the git client directly accesses LFS content at the shared network directory
file:///node/
Git Remote: file:////unc_dir/bar
LFS Server: none; the git client directly accesses LFS content in the Windows UNC path \\unc_dir\
.
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/
LFS Server: https://git-server.com/
Git Remote: https://git-server.com/
LFS Server: https://git-server.com/
Git Remote: git@git-server.com:foo/
LFS Server: https://git-server.com/
Git Remote: ssh://git-server.com/
LFS Server: https://git-server.com/
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:
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:
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:
mslinn@Bear my_repo $git config -f .lfsconfig lfs.url \ http://my_lfs_server/api/my_org/my_repo
Breaking down the above incantation:
-
git config
: This configuration value is local to the current git repository, so it is not a global setting. -
-f .lfsconfig
: Write this configuration value to the file.lfsconfig
, not the default file (.git/config).
-
lfs.url
: This is the name of a morsel of client-side Git LFS configuration data. -
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
:
[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:
mslinn@Bear my_repo $git config -f .lfsconfig lfs.url http://gojira:9876/mslinn/myrepo
.lfsconfig
should now look like this:
[lfs] url = http://gojira:9876/mslinn/myrepo
Now commit the changes:
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:
mslinn@Bear ~ $git clone "file:///my_local_directory"
The following is equivalent to the above incantation:
mslinn@Bear ~ $git clone "file://localhost/my_local_directory"
You could also specify the same repository without the file:///
prefix:
mslinn@Bear ~ $git clone "my_local_directory"
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
:
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
:
mslinn@Bear ~ $git clone "file:////GOJIRA/git_directory"
Authentication
Two Approaches
There are two possible approaches for using Git LFS over SSH.
-
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. -
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 thegit-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.– From Git LFS documentation
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:
-
Scutiger published the original version of
git-lfs-transfer
on 2019-03-09. Within that repository, thescutiger-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. -
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
zlib
,
the 8-bit version of libpcre2
,
and libgit2
available,
Scutiger will dynamically link against them.
This is highly recommended for security reasons.
The build process is very easy:
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/
:
mslinn@gojira scutiger $ls ~/.cargo/bin git-lfs-transfer*
The help information is incomplete:
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
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/
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:
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:
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
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
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:
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.
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:
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:
[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:
$ ssh mslinn@gojira git-lfs-transfer /mnt/_/work/git/eval_bare_repo download
When I type the above incantation, the response is:
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.
- Git Large File System Overview
- Git LFS Client Installation
- Git LFS Server URLs
- Git LFS Filename Patterns & Tracking
- Git LFS Client Configuration & Commands
- Working With Git LFS
- 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.