Git and libgit2

Null Git LFS Server

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

This page is part of the git collection.

I have published 8 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 Scripts
  3. Git LFS Client Installation
  4. Git LFS Server URLs
  5. Git-ls-files, Wildmatch Patterns and Permutation Scripts
  6. Git LFS Tracking, Migration and Un-Migration
  7. Git LFS Client Configuration & Commands
  8. Git LFS SSH Authentication
  9. Working With Git LFS
  10. Evaluation Procedure For Git LFS Servers
  11. Git LFS server tests:
    1. Null Git LFS Server

6 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.

This Article Probably Contains Errors

This article is incomplete and may contain errors. It has been published to allow collaboration with fact-checkers. Not all of these scenarios might make sense to attempt to review. Do not rely on this information yet.

Null Servers

Of all the available Git LFS server choices, the option to do without a Git LFS server is the simplest to set up. In fact, you can do away with the Git server also and use a bare Git repository instead. You will lose the file locking feature, but few people use it.

A null Git LFS server could be set up for an individual or organization if a local area network is already set up and at least one machine is always available as a server. I have written this article with that assumption in mind.

For a null Git LFS server, all that is required is a network-accessible directory structure to store large files.

No client-side Git LFS filters or hooks are required

Use Cases

Doing without a Git LFS server has the following common use cases:

  1. File size overwhelms available bandwidth.
  2. Extremely sensitive documents, copies of which must not leave the building.
  3. Simplest possible Git LFS setup.

The following scenarios are complete examples of different ways of setting up null Git LFS servers.

  1. A locally accessible bare Git repository and a separate locally accessible directory for large files.
  2. A remote Git repository, such as GitHub, with a locally accessible directory for large files.

Restrictions & Requirements

  1. Only one concurrent user with write access.
  2. A computer is dedicated to acting as a server.

My Setup

I am the only person who has write access to my files.

I have an Ubuntu server in my apartment called gojira that is a media store, runs Plex, and is a terrific programming environment. This machine is normally never turned off. It acts as a Samba server, runs an nginx web server, etc. I used gojira as a Git LFS server-side test platform. Gojira has a dedicated UPS and is mirrored to a backup machine called bambam.

I also have a laptop for working while traveling, and another laptop for performing on-location computer music. Both of these laptops need Git LFS, although the types of information stored on each are different.

I also have a desktop computer (bear) that I wrote most of the website on. Bear needs to access everything that all the other machines have access to.

My user ID on all machines is mslinn.

Bare Git Repositories

Bare Git Repositories discusses bare Git repositories and introduces the new_bare_repo script that we will use shortly.

It is possible to employ Git LFS with a bare Git repository, so long as the bare Git repository is accessible via a supported protocol. The script makes the empty bare repository that we will need for each test scenario.

Scenarios Considered

The following scenarios were considered for this evaluation.

Git Server Scenario Git LFS Protocol Use Cases
None (bare repo) 1 local Not functional.
  • Requires networking
  • External access is not required
  • Provides ultra security
  • Supports ultra-large files
2 SSH
  • Can provide strong security
  • Supports ultra-large files
  • Supports external access
  • Setup can be simple or complex
GitHub 3 local
  • External access is not required
  • Supports ultra-large files
  • Simplest setup if network is already set up
4 SSH
  • Can provide strong security
  • Supports ultra-large files
  • Supports external access
  • Setup can be simple or complex
5 http
  • Requires DNS setup
  • Requires a web server on the internal network
  • The gateway provides the only security; no other authentication is possible when accessing the internal network
  • Supports external access

Scenario 1: Locally Accessible Bare Git Repository

Git Server Scenario Git LFS Protocol Use Cases
None (bare repo) 1 local Not functional.
  • Requires networking
  • External access is not required
  • Provides ultra security
  • Supports ultra-large files

I am still working on this scenario.

The bare repository for this test was created on Ubuntu server gojira in ~/eval_repos/repo1.git/. A new directory called ~/eval_repos/repo1.lfs was created to hold Git LFS data.

The git protocol is unavailable in this scenario because there is no Git server.

Shell
mslinn@gojira ~ $ mkdir -p ~/eval_repos && cd ~/eval_repos
mslinn@gojira eval_repos $ mkdir repo1.lfs
mslinn@gojira eval_repos $ new_bare_repo repo1.git Initialized empty shared Git repository in /home/mslinn/eval_repos/repo1.git/ The new repository contains the following subdirectory tree . ├── hooks ├── info ├── objects │   ├── info │   └── pack └── refs ├── heads └── tags 9 directories All done! The bare repository was created in '/home/mslinn/eval_repos/repo1.git'.

My desktop computer bear will contain a similar tree of test repositories, mirroring the server-side organization. Instead of being rooted in my home directory, the tree is rooted at $work.

Shell
mslinn@bear ~ $ mkdir -p $work/eval_repos && cd $work/eval_repos

My home directory on gojira is mounted on my desktop computer bear at /mnt/gojira_mslinn. Thus, the path to the bare Git repository on gojira from the desktop computer is /mnt/gojira_mslinn/eval_repos/repo1.git.

Let’s make sure the bare Git repository is on the server gojira in the expected directory:

Shell
mslinn@bear eval_repos $ ls /mnt/gojira_mslinn/eval_repos/repo1.git
HEAD*  config*  description*  hooks/  info/  objects/  refs/ 

Let’s also ensure that the directory for the null Git LFS repository is also where we expect it to be. This directory will be used as the value of lfs.url in a moment.

Shell
mslinn@bear eval_repos $ ls -l /mnt/gojira_mslinn/eval_repos/repo1.lfs
total 0 

Now we can clone the bare Git repository.

Shell
mslinn@bear eval_repos $ git clone /mnt/gojira_mslinn/eval_repos/repo1.git
Cloning into 'repo1'...
warning: You appear to have cloned an empty repository.
done. 
mslinn@bear eval_repos $ cd repo1
mslinn@bear repo1 $

This is the first opportunity to establish lfs.url. If we forget to do this, the tracked large files will end up in the Git repository, instead of the Git LFS repository.

Shell
mslinn@bear repo1 $ git config -f .lfsconfig lfs.url \
  /mnt/gojira_mslinn/eval_repos/repo1.lfs
mslinn@bear repo1 $ cat .lfsconfig [lfs] url = /mnt/gojira_mslinn/eval_repos/repo1.lfs

Now let’s populate the repository with the first set of test data, stored in the git_lfs_test_data/v1/ directory.

Shell
mslinn@bear repo1 $ rsync -at --progress "$work/git/git_lfs_test_data/v1/" ./
sending incremental file list ./ 200MB.zip 209,715,200 100% 357.09MB/s 0:00:00 (xfr#1, to-chk=9/11) BigBuckBunny_640x360.m4v 121,283,919 100% 133.41MB/s 0:00:00 (xfr#2, to-chk=8/11) big_buck_bunny_480p_h264.mov 106,491,904 100% 89.56MB/s 0:00:01 (xfr#3, to-chk=7/11) big_buck_bunny_480p_stereo.avi 156,506,028 100% 281.61MB/s 0:00:00 (xfr#4, to-chk=6/11) big_buck_bunny_480p_stereo.ogg 166,825,767 100% 165.73MB/s 0:00:00 (xfr#5, to-chk=5/11) big_buck_bunny_720p_h264.mov 416,751,190 100% 199.02MB/s 0:00:01 (xfr#6, to-chk=4/11) big_buck_bunny_720p_stereo.avi 284,437,944 100% 157.80MB/s 0:00:01 (xfr#7, to-chk=3/11) big_buck_bunny_720p_stereo.ogg 196,898,674 100% 154.80MB/s 0:00:01 (xfr#8, to-chk=2/11) enwik9.zip 322,592,222 100% 305.21MB/s 0:00:01 (xfr#9, to-chk=1/11) rdf-files.tar.zip 160,912,831 100% 372.47MB/s 0:00:00 (xfr#10, to-chk=0/11)

Let’s see what we have in this repository now:

Shell
mslinn@bear repo1 $ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        200MB.zip
        BigBuckBunny_640x360.m4v
        README.md
        big_buck_bunny_480p_h264.mov
        big_buck_bunny_480p_stereo.avi
        big_buck_bunny_480p_stereo.ogg
        big_buck_bunny_720p_h264.mov
        big_buck_bunny_720p_stereo.avi
        big_buck_bunny_720p_stereo.ogg
        enwik9.zip
        rdf-files.tar.zip 

Git LFS must be informed of the patterns to track.

Shell
mslinn@bear repo1 $ track -ce avi m4v mov ogg pdf zip
Tracking "*.avi"
Tracking "*.AVI"
Tracking "**/*.avi"
Tracking "**/*.AVI"
Tracking "*.m4v"
Tracking "*.M4V"
Tracking "**/*.m4v"
Tracking "**/*.M4V"
Tracking "*.mov"
Tracking "*.MOV"
Tracking "**/*.mov"
Tracking "**/*.MOV"
Tracking "*.ogg"
Tracking "*.OGG"
Tracking "**/*.ogg"
Tracking "**/*.OGG"
Tracking "*.pdf"
Tracking "*.PDF"
Tracking "**/*.pdf"
Tracking "**/*.PDF"
Tracking "*.zip"
Tracking "*.ZIP"
Tracking "**/*.zip"
Tracking "**/*.ZIP" 

.gitattributes was created in the current directory by the previous command. It looks like this:

.gitattributes
*.avi filter=lfs diff=lfs merge=lfs -text
*.AVI filter=lfs diff=lfs merge=lfs -text
**/*.avi filter=lfs diff=lfs merge=lfs -text
**/*.AVI filter=lfs diff=lfs merge=lfs -text
**/*.M4V filter=lfs diff=lfs merge=lfs -text
*.m4v filter=lfs diff=lfs merge=lfs -text
*.M4V filter=lfs diff=lfs merge=lfs -text
**/*.m4v filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text
*.MOV filter=lfs diff=lfs merge=lfs -text
**/*.mov filter=lfs diff=lfs merge=lfs -text
**/*.MOV filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.OGG filter=lfs diff=lfs merge=lfs -text
**/*.ogg filter=lfs diff=lfs merge=lfs -text
**/*.OGG filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.ZIP filter=lfs diff=lfs merge=lfs -text
**/*.zip filter=lfs diff=lfs merge=lfs -text
**/*.ZIP filter=lfs diff=lfs merge=lfs -text

It took about 11 seconds to add all files to the next commit. This is two orders of magnitude faster than the same operation would have taken with Plain Old Git.

Shell
mslinn@bear repo1 $ time git add -A
real    0m11.336s
user    0m3.542s
sys     0m2.984s 

Commits are almost instant, no need to time them.

Shell
mslinn@bear repo1 $ git commit -m -
[master (root-commit) 4ced4f2] -
12 files changed, 51 insertions(+)
create mode 100644 .gitattributes
create mode 100644 200MB.zip
create mode 100644 BigBuckBunny_640x360.m4v
create mode 100644 README.md
create mode 100644 big_buck_bunny_480p_h264.mov
create mode 100644 big_buck_bunny_480p_stereo.avi
create mode 100644 big_buck_bunny_480p_stereo.ogg
create mode 100644 big_buck_bunny_720p_h264.mov
create mode 100644 big_buck_bunny_720p_stereo.avi
create mode 100644 big_buck_bunny_720p_stereo.ogg
create mode 100644 enwik9.zip
create mode 100644 rdf-files.tar.zip 

This is where Git LFS really shows its value. Committing more 2GB at once is not possible with Plain Old Git, because pack sizes cannot exceed 2GB.

Shell
mslinn@bear repo1 $ time git push -u origin master
Uploading LFS objects: 100% (10/10), 0 B | 0 B/s, done.
Enumerating objects: 14, done.
Counting objects: 100% (14/14), done.
Delta compression using up to 24 threads
Compressing objects: 100% (13/13), done.
Writing objects: 100% (14/14), 1.91 KiB | 97.00 KiB/s, done.
Total 14 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To /mnt/gojira_mslinn/git_eval_repos/repo1.git
* [new branch] master -> master
branch 'master' set up to track 'origin/master'.

real 0m43.957s
user 0m0.336s
sys 0m0.839s 

As you can see, one of the files being pushed is 200 MB. This is twice the 100 MB file size restriction that online vendors have for Plain Old Git. In total, this Git push was 2 GB; however, it only took 34 seconds.

Exercise the Git LFS Repository

I moved to my Windows WSL laptop, called camille. It already had Git LFS installed. The steps for the remainder of this evaluation are:

  1. Copied my gojira_credentials file (which was created in Mounting Shared Directories on WSL & Ubuntu) to my home directory on camille.
  2. Created the /mnt/gojira_mslinn mount point.
  3. Made an entry to define the /mnt/gojira_mslinn mount point in /etc/fstab as before for mounting the shared directory on gojira containing the Git repository.
  4. Mounted the shared directory tree /mnt/gojira_mslinn
  5. Cloned containing the Gif LFS repository
  6. Make a change to a small file and a large file
  7. Commit and push
  8. Return to the desktop and pull the changes.
Shell
mslinn@camille ~ $ scp gojira:gojira_credentials ~/
mslinn@camille ~ $ sudo mkdir /mnt/gojira_mslinn
mslinn@camille ~ $ echo \ "//gojira/mslinn /mnt/gojira_mslinn drvfs credentials=/home/mslinn/gojira_credentials,defaults,gid=1000,uid=1000" | \ sudo tee -a /etc/fstab > /dev/null
mslinn@camille ~ $ sudo systemctl daemon-reload
mslinn@camille ~ $ sudo mount /mnt/gojira_mslinn
mslinn@camille ~ $ mkdir $work/git_lfs && cd $work/git_lfs
mslinn@camille git_lfs $ time git clone /mnt/gojira_mslinn/eval_repos/repo1.git Cloning into 'repo1'... done. Filtering content: 100% (10/10), 1.99 GiB | 32.30 MiB/s, done. real 1m10.895s user 0m2.397s sys 0m4.482s

The git clone command behaved differently than usual. This message appeared within a second:

Initial output of above git clone command
Cloning into 'repo1'...
done.

But the computer continued to churn away for 70 seconds more while it downloaded Git LFS content, until that concluded with:

Remainder of output of above git clone command
Filtering content: 100% (10/10), 1.99 GiB | 32.30 MiB/s, done.

Now let’s verify that all the large objects are stored in Git LFS. An asterisk (*) after the OID indicates a full object, while a minus (-) indicates an LFS pointer.

Continuing the console session
mslinn@camille git_lfs $ cd repo1
mslinn@camille repo1 $ git lfs ls-files -s d14b731506 * 200MB.zip (210 MB) 738e2f9998 * BigBuckBunny_640x360.m4v (121 MB) 98959d8b27 * big_buck_bunny_480p_h264.mov (106 MB) 4fc75fa403 * big_buck_bunny_480p_stereo.avi (156 MB) 9b9867582c * big_buck_bunny_480p_stereo.ogg (167 MB) 45c8bafeb9 * big_buck_bunny_720p_h264.mov (417 MB) 919e19d85c * big_buck_bunny_720p_stereo.avi (284 MB) 785b09a585 * big_buck_bunny_720p_stereo.ogg (197 MB) 99cdb5ac84 * enwik9.zip (323 MB) c5608dae76 * rdf-files.tar.zip (161 MB)
😖 😖

None of the large files are shown as being stored in Git LFS. This is because null servers cannot provide a callback, so the Git client is not notified when a large file should be handled as Git LFS content.

Update this if required.

Scenario 2: Remote Git Repository With Locally Accessible Large Files

Git Server Scenario Git LFS Protocol Use Cases
2 SSH
  • Can provide strong security
  • Supports ultra-large files
  • Supports external access
  • Setup can be simple or complex

There are several equivalent syntaxes for specifying a Git URL for SSH. They provide identical results. Use the one that pleases you most. I tend to use the shortest URL possible.

Clone The Bare Repository On a Client Machine

A copy of the empty bare repository was placed in $HOME/repo2 on server gojira. The following uses brace expansion as a shorthand expression.

Shell
mslinn@gojira ~ cp -a bare_repo{,1}

Clones of bare Git repositories are normal Git repositories.

Back on my desktop computer called bear, I performed the following on WSL/Ubuntu:

  1. Cloned the bare repository repo2 on the server called gojira to a new directory on my desktop computer bear, into a new directory under the $work directory.
    Shell
    mslinn@bear ~ $ mkdir $work/git_lfs && cd $work/git_lfs
    mslinn@bear git_lfs $ git clone gojira:repo2 Cloning into 'repo2'... warning: You appear to have cloned an empty repository.
    mslinn@bear git_lfs $ cd repo2
    mslinn@bear repo2 $
  2. No hooks are required because there is no Git server and no Git LFS server in this scenario, so there will be no callbacks. I included the git lfs install command because of the copy/paste habit that so many people have. These instructions should work even if you use these instructions with a non-null Git LFS server, provided that a suitable URL is used.
    Shell
    mslinn@bear repo2 $ git lfs install # No-op
    Updated Git hooks.
    Git LFS initialized. 

Let's see what the local copy of the bare Git repository looks like at this point:

Shell
mslinn@bear repo2 $ git config remote.origin.url
gojira:repo2 
mslinn@bear repo2 $ ls # No files at present
mslinn@bear repo2 $ tree -ad . └── .git ├── branches ├── hooks ├── info ├── objects │   ├── info │   └── pack └── refs ├── heads └── tags
13 directories

Set Up Tracking

All large files need to be tracked. The patterns you provide via the git lfs track command specify the filename patterns to be tracked. Because media filenames might be in UPPERCASE or lowercase, the patterns for media files are specified in lowercase and UPPERCASE using the wp script.

It is best to set up Git LFS tracking before adding large files to the Git repository. The large files downloaded by git_lfs_test_data will be copied into this Git repository in the next section.

Shell
mslinn@bear repo2 $ track -ce avi m4v mov ogg zip
Tracking "*.avi"
Tracking "*.AVI"
Tracking "**/*.avi"
Tracking "**/*.AVI"
Tracking "*.m4v"
Tracking "*.M4V"
Tracking "**/*.m4v"
Tracking "**/*.M4V"
Tracking "*.mov"
Tracking "*.MOV"
Tracking "**/*.mov"
Tracking "**/*.MOV"
Tracking "*.ogg"
Tracking "*.OGG"
Tracking "**/*.ogg"
Tracking "**/*.OGG"
Tracking "*.zip"
Tracking "*.ZIP"
Tracking "**/*.zip"
Tracking "**/*.ZIP" 

The git lfs track command created .gitattributes and stored the wildcard patterns into it.

.gitattributes
*.ZIP filter=lfs diff=lfs merge=lfs -text
**/*.zip filter=lfs diff=lfs merge=lfs -text
*.MOV filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
**/*.ogg filter=lfs diff=lfs merge=lfs -text
**/*.OGG filter=lfs diff=lfs merge=lfs -text
*.avi filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text
**/*.mov filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
**/*.ZIP filter=lfs diff=lfs merge=lfs -text
*.AVI filter=lfs diff=lfs merge=lfs -text
**/*.AVI filter=lfs diff=lfs merge=lfs -text
*.M4V filter=lfs diff=lfs merge=lfs -text
**/*.m4v filter=lfs diff=lfs merge=lfs -text
*.OGG filter=lfs diff=lfs merge=lfs -text
**/*.avi filter=lfs diff=lfs merge=lfs -text
*.m4v filter=lfs diff=lfs merge=lfs -text
**/*.M4V filter=lfs diff=lfs merge=lfs -text
**/*.MOV filter=lfs diff=lfs merge=lfs -text

The git lfs track command also created the .git/lfs directory.

Shell
mslinn@bear repo2 $ tree -ad
.
└── .git
    ├── branches
    ├── hooks
    ├── info
    ├── lfs
    │   └── tmp
    ├── objects
    │   ├── info
    │   └── pack
    └── refs
        ├── heads
        └── tags
13 directories

Add Content

It is time to add content to the Git repository:

  • Create a normal file.
  • Copy the large files from the git_lfs_test_data script into the local copy of the repo2 Git repository.
Shell
mslinn@bear repo2 $ echo 'Hello world' > normal_file.txt
mslinn@bear repo2 $ time scp gojira:/work/git/git_lfs_test_data/* . 200MB.zip 100% 200MB 114.2MB/s 00:01 BigBuckBunny_640x360.m4v 100% 116MB 111.5MB/s 00:01 big_buck_bunny_480p_h264.mov 100% 102MB 111.4MB/s 00:00 big_buck_bunny_480p_stereo.avi 100% 149MB 113.2MB/s 00:01 big_buck_bunny_480p_stereo.ogg 100% 159MB 118.9MB/s 00:01 big_buck_bunny_720p_h264.mov 100% 397MB 115.0MB/s 00:03 big_buck_bunny_720p_stereo.avi 100% 271MB 111.8MB/s 00:02 big_buck_bunny_720p_stereo.ogg 100% 188MB 113.9MB/s 00:01 enwik9.zip 100% 308MB 115.1MB/s 00:02 rdf-files.tar.zip 100% 153MB 114.9MB/s 00:01 real 0m18.208s user 0m2.456s sys 0m2.433s

I previously mentioned that the git add command is slow when working with large files. However, when large files are added to Git LFS, the process only takes as long as necessary to place a copy of the large files in .git/lfs/objects.

Shell
mslinn@bear repo2 $ time git add -A
real 0m11.237s
user 0m3.315s
sys 0m2.793s 

As we know, gitadd takes snapshots of normal files and saves them in the staging area (.git/index). This repository only has one normal file (normal_file.txt), so the Git index is small right now:

Shell
$ du -h .git/index
4.0K .git/index 

The large tracked files that were just added should have been snapshotted and saved to .git/lfs/objects. The last line of the following output is highlighted, and shows that the total amount of data staged is 2 GB, which, as you may recall, is the total amount of bunny videos that were downloaded.

Shell
mslinn@bear repo2 $ du -h .git/lfs/objects
398M .git/lfs/objects/45/c8
398M .git/lfs/objects/45
150M .git/lfs/objects/4f/c7
150M .git/lfs/objects/4f
116M .git/lfs/objects/73/8e
116M .git/lfs/objects/73
188M .git/lfs/objects/78/5b
188M .git/lfs/objects/78
272M .git/lfs/objects/91/9e
272M .git/lfs/objects/91
102M .git/lfs/objects/98/95
102M .git/lfs/objects/98
308M .git/lfs/objects/99/cd
308M .git/lfs/objects/99
160M .git/lfs/objects/9b/98
160M .git/lfs/objects/9b
154M .git/lfs/objects/c5/60
154M .git/lfs/objects/c5
200M .git/lfs/objects/d1/4b
200M .git/lfs/objects/d1
2.0G .git/lfs/objects 

We can even view the videos stored in the Git LFS staging area!

Shell
mslinn@bear repo2 $ vlc .git/lfs/objects/45/c8/45c8bafeb9a53df7f491198d2e71529701bcf1cd51805782089fac1d32869f9b

Commit

Now the files need to be committed; this command executes quickly.

Shell
mslinn@bear repo2 $ time git commit -m 'Added a normal file and large files'
[master (root-commit) 7d0fe8c] Added a normal file and large files
12 files changed, 51 insertions(+)
create mode 100644 .gitattributes
create mode 100644 200MB.zip
create mode 100644 BigBuckBunny_640x360.m4v
create mode 100644 big_buck_bunny_480p_h264.mov
create mode 100644 big_buck_bunny_480p_stereo.avi
create mode 100644 big_buck_bunny_480p_stereo.ogg
create mode 100644 big_buck_bunny_720p_h264.mov
create mode 100644 big_buck_bunny_720p_stereo.avi
create mode 100644 big_buck_bunny_720p_stereo.ogg
create mode 100644 enwik9.zip
create mode 100644 normal_file.txt
create mode 100644 rdf-files.tar.zip

real 0m0.327s
user 0m0.016s
sys 0m0.027s 

Push Changes To The Bare Repository

The files are ready to be pushed to the bare repository. Only normal_file.txt will be handled normally, while the others will be handled as large files.

Shell
mslinn@bear repo2 $ time git push -u origin master
Locking support detected on remote "origin". Consider enabling it with:
  $ git config lfs.https://gojira/home/mslinn/repo2.git/info/lfs.locksverify true
Uploading LFS objects: 100% (10/10), 2.1 GB | 115 MB/s, done.
Enumerating objects: 14, done.
Counting objects: 100% (14/14), done.
Delta compression using up to 24 threads
Compressing objects: 100% (13/13), done.
Writing objects: 100% (14/14), 1.93 KiB | 123.00 KiB/s, done.
Total 14 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To ssh://gojira/home/mslinn/repo2
 * [new branch]      master -> master
branch 'master' set up to track 'origin/master'.

real    0m19.457s
user    0m3.541s
sys     0m3.557s 

That happened quickly!

I do not believe the Locking support detected message since http is not being used. If you type the git config command as suggested, the warning goes away, which is fine, but nothing changes unless http is used.

The above pushed LFS objects to gojira:/home/mslinn/repo2/.git/lfs/objects. We can see the same IDs for the large files in both copies of the repository.

Client-side (bear)
mslinn@bear repo2 $ tree .git/lfs/
.git/lfs/
.git/lfs/
├── cache
│   └── locks
│       └── refs
│           └── heads
│               └── master
│                   └── verifiable
├── objects
│   ├── 45
│   │   └── c8
│   │       └── 45c8bafeb9a53df7f491198d2e71529701bcf1cd51805782089fac1d32869f9b
│   ├── 4f
│   │   └── c7
│   │       └── 4fc75fa403994e7c313da139d93a5aebdbda27cc951616aa4e480db6877c9850
│   ├── 73
│   │   └── 8e
│   │       └── 738e2f999860553d056dd79c952f58f63cbb73892a57c72342ce9e5330d9d2d7
│   ├── 78
│   │   └── 5b
│   │       └── 785b09a585be55f81326a3fcef2cdeeb7ebbc33932b6305fd84209928df67f28
│   ├── 91
│   │   └── 9e
│   │       └── 919e19d85c51e8f07074f9e51127c103280d7623c2b6696f2621b4fca6850e76
│   ├── 98
│   │   └── 95
│   │       └── 98959d8b270c6c35c9e2c906559a47c49a0e1491c8c1d6e5576164f3549aeea6
│   ├── 99
│   │   └── cd
│   │       └── 99cdb5ac84392252d3f0912ccedd195bc95bd80cbef3b0cdf2eee4ad9a3b7a51
│   ├── 9b
│   │   └── 98
│   │       └── 9b9867582cc9cf88b03f8a2065c4f86ec17e650e65c369e1ea4d0d37e5df5da6
│   ├── c5
│   │   └── 60
│   │       └── c5608dae76ef836dfe8af2651d3869a2db0c3a9015ac43fb8d17f72499d17c58
│   └── d1
│       └── 4b
│           └── d14b73150642f30d2342e6620fa537ea273a58b8b751fc5af8f4aabe809f8fc4
└── tmp

28 directories, 11 files 

The same files exist in the .git/lfs/objects directory on the server.

Server-side (gojira)
mslinn@bear repo2 $ ssh gojira tree repo2/.git/lfs/objects
repo2/.git/lfs/objects
├── 45
│   └── c8
│       └── 45c8bafeb9a53df7f491198d2e71529701bcf1cd51805782089fac1d32869f9b
├── 4f
│   └── c7
│       └── 4fc75fa403994e7c313da139d93a5aebdbda27cc951616aa4e480db6877c9850
├── 73
│   └── 8e
│       └── 738e2f999860553d056dd79c952f58f63cbb73892a57c72342ce9e5330d9d2d7
├── 78
│   └── 5b
│       └── 785b09a585be55f81326a3fcef2cdeeb7ebbc33932b6305fd84209928df67f28
├── 91
│   └── 9e
│       └── 919e19d85c51e8f07074f9e51127c103280d7623c2b6696f2621b4fca6850e76
├── 98
│   └── 95
│       └── 98959d8b270c6c35c9e2c906559a47c49a0e1491c8c1d6e5576164f3549aeea6
├── 99
│   └── cd
│       └── 99cdb5ac84392252d3f0912ccedd195bc95bd80cbef3b0cdf2eee4ad9a3b7a51
├── 9b
│   └── 98
│       └── 9b9867582cc9cf88b03f8a2065c4f86ec17e650e65c369e1ea4d0d37e5df5da6
├── c5
│   └── 60
│       └── c5608dae76ef836dfe8af2651d3869a2db0c3a9015ac43fb8d17f72499d17c58
└── d1
    └── 4b
        └── d14b73150642f30d2342e6620fa537ea273a58b8b751fc5af8f4aabe809f8fc4
21 directories, 10 files

Clone The Bare Repository Again

Scenario 3: GitHub With Locally Accessible Large Files

Git Server Scenario Git LFS Protocol Use Cases
GitHub 3 local
  • External access is not required
  • Supports ultra-large files
  • Simplest setup if network is already set up

Scenario 4: GitHub With Remotely Accessible Large Files Over SSH

Git Server Scenario Git LFS Protocol Use Cases
4 SSH
  • Can provide strong security
  • Supports ultra-large files
  • Supports external access
  • Setup can be simple or complex

I have had good results with Git LFS over SSH when SSH keys have been deployed throughout the network. Without SSH keys, an authentication mechanism would be required.

Git Server Scenario Git LFS Protocol Use Cases
5 http
  • Requires DNS setup
  • Requires a web server on the internal network
  • The gateway provides the only security; no other authentication is possible when accessing the internal network
  • Supports external access

I have published 8 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 Scripts
  3. Git LFS Client Installation
  4. Git LFS Server URLs
  5. Git-ls-files, Wildmatch Patterns and Permutation Scripts
  6. Git LFS Tracking, Migration and Un-Migration
  7. Git LFS Client Configuration & Commands
  8. Git LFS SSH Authentication
  9. Working With Git LFS
  10. Evaluation Procedure For Git LFS Servers
  11. Git LFS server tests:
    1. Null Git LFS Server

6 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.