Git and libgit2

Null Git LFS Server

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

This page is part of the git collection.

I have published 7 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-ls-files and Wildmatch
  5. Git LFS Filename Patterns & Tracking
  6. Git LFS Client Configuration & Commands
  7. Working With Git LFS
  8. Evaluation Procedure For Git LFS Servers
  9. 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 Page 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

By convention, the name of the directory containing a bare Git repository ends with .git. However, this is not required.

Bare repositories do not have a working tree, so you cannot add files to them as you would for a normal repository. Instead, you must update a bare repository by pushing to it from a clone of the repository.

When to Use

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. That means the bare Git repository must either be stored on the local machine, or on a local area network. Normally, one would store bare Git repositories on a local area network server.

The advantages of this scenario are:

  1. Security can be better than storing files in the cloud.
  2. This is the simplest way to use Git LFS; because only file servers are involved; the Git client does all the Git-related work.

Working with bare repositories is much simpler than the setup for all other configurations because no Git server and no Git LFS server are involved.

The git init ‑‑shared Option

The article entitled Shared Directories With POSIX Groups and SGID discusses POSIX groups and the SGID permission bit. This section uses the git_access POSIX group created in that article.

The most convenient way to allow others to push to a bare Git repository is to use the git init ‑‑shared option. This option causes the SGID bit to be set on the directory holding the Git repository. The general format is:

Shell
$ git --bare --shared=some_value init

The --shared option can have several values. The possible values that are relevant to a locally accessible Git LFS from a bare repository are: all, everybody, group, true, and world.

When the ‑‑shared option is specified, the config variable core.sharedRepository is set to 1 for group sharing, and is set to 2 for sharing with everybody.

Otherwise, if the ‑‑shared option is not specified, Git will use the permissions reported by umask. See the man page for git-init for further details.

If you specify ‑‑shared=group or ‑‑shared=true, the Set Group ID (SGID) permission for that directory will be set, as shown in the highlighted s permission bit in the code example below. Note that test_repo.git is created in the home directory of user mslinn for this example.

Shell
mslinn@gojira ~ mkdir --mode=g+s test_repo.git
mslinn@gojira ~ sudo chgrp git_access test_repo.git
mslinn@gojira ~ ls -ld test_repo.git drwxrwsrwx 2 mslinn git_access 4096 Jan 20 13:51 test_repo.git/
mslinn@gojira ~ git init --bare --shared=group test_repo.git Initialized empty shared Git repository in /home/mslinn/test_repo.git
mslinn@gojira ~ ls -l test_repo.git total 28 -rw-rw-r-- 1 mslinn git_access 126 Jan 20 09:34 config -rw-rw-r-- 1 mslinn git_access 73 Jan 20 09:34 description -rw-rw-r-- 1 mslinn git_access 23 Jan 20 09:34 HEAD drwxrwsr-x 2 mslinn git_access 4096 Jan 20 09:34 hooks/ drwxrwsr-x 2 mslinn git_access 4096 Jan 20 09:34 info/ drwxrwsr-x 4 mslinn git_access 4096 Jan 20 09:34 objects/ drwxrwsr-x 4 mslinn git_access 4096 Jan 20 09:34 refs/
mslinn@gojira ~ cd test_repo.git
mslinn@gojira test_repo.git git config core.sharedRepository 1

Now lets clone the new repo from another computer, a Windows machine with WSL, which has had /etc/fstab augmented with the necessary incantation to mount my home directory on gojira on the WSL file system.

See Mounting Shared Directories on WSL & Ubuntu for background on mounting shared drives on Ubuntu and WSL/Ubuntu. The remainder of this article assumes you have read the background article and it is fresh in your mind. Ubuntu on WSL is just a little bit different from native Ubuntu in how shared directories are mounted. The very small difference is quite important to get right.

Shell
mslinn@bear ~ sudo mount /mnt/gojira_mslinn
mslinn@bear ~ ls /mnt/gojira_mslinn/test_repo.git/ HEAD* config* description* hooks/ info/ objects/ refs/
mslinn@bear ~ git clone /mnt/gojira_mslinn/test_repo.git Cloning into 'test_repo'... warning: You appear to have cloned an empty repository. done.

You can open up the permissions such that anyone with access can update the repository. This is the simplest configuration. There are three equivalent ways of specifying this value: ‑‑shared=all, ‑‑shared=world and ‑‑shared=everybody.

Shell
mslinn@gojira ~ mkdir --mode=g+s test_repo2.git
mslinn@gojira ~ sudo chgrp git_access test_repo2.git
mslinn@gojira ~ git init --bare --shared=everybody test_repo2.git Initialized empty shared Git repository in /home/mslinn/test_repo2.git
mslinn@gojira ~ cd test_repo2.git
mslinn@gojira test_repo.git git config core.sharedRepository 2

We can clone test_repo2.git from the other computer as before. The mount at /mnt/gojira_mslinn is assumed to still be in place.

Shell
mslinn@bear ~ ls /mnt/gojira_mslinn/test_repo2.git
HEAD* config* description* hooks/ info/ objects/ refs/ 
mslinn@bear ~ git clone /mnt/gojira_mslinn/test_repo2.git Cloning into 'test_repo2'... warning: You appear to have cloned an empty repository. done.

receive.denyCurrentBranch

You could set the receive.denyCurrentBranch configuration variable to ignore or updateInstead. While this does nothing for a null Git LFS server, it eliminates a warning that is meaningless for this configuration. We will see this in action later.

newBareRepo Creation Script

The following script creates a bare Git repository with --shared=all:

newBareRepo
#!/bin/bash

function help {
  if [ "$1" ]; then echo "$1"; fi
  echo "$(basename "$0") - Create a new bare git repository.

Syntax: $(basename "$0") /path/to/new/repo

A new git repository will be created in /path/to/new/repo,
which should not already exist.
The parent directory (/path/to/new/) must already exist.
The name of the repo must not contain spaces.

After creation, a new environment variable will be created
in /etc/environment.
The name that you specified ('repo' in the above example) will be used
for the name of a new environment variable, set to the path that
you specified.
"
  exit 1
}

if [ -z "$1" ]; then help; fi

export REPO_PATH="$(dirname "$1")"
if [ "$REPO_PATH" == ~ ]; then REPO_PATH="$HOME"; fi

export REPO_NAME="$( basename "$1" )"

if [ ! -d "$REPO_PATH" ]; then help "Error: '$REPO_PATH' does not exist."; fi
if [ -f "$REPO_PATH/$REPO_NAME" ]; then help "Error: '$PATH/$REPO_NAME' already exists."; fi

echo "$REPO_NAME=$REPO_PATH/$REPO_NAME" | \
  sudo tee -a /etc/environment > /dev/null

git init --bare --shared=all "$REPO_PATH/$REPO_NAME"
chmod 777 "$REPO_PATH/$REPO_NAME" # Modify this to suit your security needs
cd "$REPO_PATH/$REPO_NAME" || exit 3

# git config receive.denyCurrentBranch ignore

if [ "$( which tree )" ]; then
  echo "The new repository contains the following subdirectory tree"
  tree -d
fi
echo "All done! The bare repository was created in '$REPO_PATH/$REPO_NAME'."

After copying newBareRepo script to a directory on the PATH, make the script executable.

Shell
mslinn@gojira ~ $ chmod a+x /usr/local/bin/newBareRepo

This is the help message:

mslinn@gojira ~ $ newBareRepo
newBareRepo - Create a new bare git repository.

Syntax: newBareRepo /path/to/new/repo

A new git repository will be created in /path/to/new/repo,
which should not already exist.
The parent directory (/path/to/new/) must already exist.
The name of the repo must not contain spaces.

After creation, a new environment variable will be created
in /etc/environment.
The name that you specified ('repo' in the above example) will be used
for the name of a new environment variable, set to the path that
you specified. 

Blow-By-Blow Explanation

The following commands make a bare repository in a new directory on gojira called $REPO_PATH. The repository will be open to all users that can access the server. You do not need to type all of this out; the next section shows how to use the above script to accomplish the same objective.

In the following console session, an environment variable is created, called REPO_PATH that points to the directory that will contain the bare git repository. I placed the definition for REPO_PATH in the /etc/environment directory so it is available to every shell, even cron jobs. I used the sudo tee -a command to append to /etc/environment, which is owned by root. The contents of /etc/environment are then incorporated into the console session with the source command.

Shell
mslinn@bear ~ $ ssh gojira
Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.8.0-35-generic x86_64)
mslinn@gojira ~ $ REPO_PATH=$HOME/bare_repo
mslinn@gojira ~ $ echo "REPO_PATH=$REPO_PATH" | \ sudo tee -a /etc/environment > /dev/null
mslinn@gojira ~ $ source /etc/environment

Now we can create the bare Git repository according to the information in the previous section.

Shell
mslinn@gojira ~ $ git init --bare --shared=all "$REPO_PATH"
Initialized empty shared git repository in /home/mslinn/bare_repo/ 
mslinn@gojira ~ $ chmod 777 "$REPO_PATH" # Modify this to suit your security needs

Alright, the bare Git repository has been created. Let’s admire our handiwork:

Shell
mslinn@gojira bare_repo $ tree -d "$REPO_PATH"
.
├── branches
├── hooks
├── info
├── objects
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags 

Using newBareRepo

Instead of typing out the commands in the previous section, you can create a bare Git repository by using the newBareRepo script:

Shell
mslinn@gojira ~ $ newBareRepo ~/bare_repo
Initialized empty shared Git repository in /home/mslinn/bare_repo/
The new repository contains the following subdirectory tree
.
├── branches
├── hooks
├── info
├── objects
│   ├── info
│   └── pack
└── refs
├── heads
└── tags
10 directories All done! The bare repository was created in '/home/mslinn/bare_repo'.

An empty bare repository will be needed for each test scenario. We will leave this bare repository untouched and make a copy in a new directory afresh for each test scenario.

Choosing a Protocol

For these scenarios, the Git remote.origin.url can be set to any suitable protocol, including a flavor of the local protocol and ssh. While you could theoretically use the http or https protocols, it would be unusual for a webserver to be configured on a local server for this purpose; however, this would work. The Git protocol is unavailable in this scenario because there is no Git server.

SSH is the most flexible protocol and is well understood. 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.

The local protocol can be expressed two ways. In the following two examples, /path/to/bare/ is a locally accessible path on a user machine. This path could originate from a locally mounted file system, as previously discussed.

Shell
mslinn@bear ~ $ sudo mount /mnt/gojira_mslinn
mslinn@bear ~ $ ls /mnt/gojira_mslinn/test_repo.git HEAD* config* description* hooks/ info/ objects/ refs/

file:///gojira_mslinn/path/to/bare/repo.git
Note the 3 slashes after file:.

Shell
mslinn@bear ~ $ git clone file:///mnt/gojira_mslinn/test_repo.git test1
Cloning into 'test1'...
warning: You appear to have cloned an empty repository. 

/gojira_mslinn/path/to/bare/repo.git

Shell
mslinn@bear ~ $ git clone /mnt/gojira_mslinn/test_repo.git test2
Cloning into 'test2'...
warning: You appear to have cloned an empty repository. 

If you are unaware of the restrictions of Git’s local protocol implementation, or the differences between the various flavors of the local protocol, please read The File URI Schema And The Local Protocol. That same article also discusses UNC paths.

Cloning On the Same Machine

On gojira, the file system containing the the bare repo is of course already mounted. The Git repository can be cloned from another local directory like this:

Shell
mslinn@gojira tmp $ git clone file:///home/mslinn/bare_repo.git
Cloning into 'bare_repo'...
warning: You appear to have cloned an empty repository. 

Also like this:

Shell
mslinn@gojira tmp $ git clone /home/mslinn/bare_repo.git
Cloning into 'bare_repo'...
warning: You appear to have cloned an empty repository. 

Scenarios Considered

The following scenarios were considered for this evaluation.

Scenario Git Server Git LFS Protocol Use Cases
1 None (bare repo) Local (file)
  • Requires networking
  • External access is not required
  • Provides ultra security
  • Supports ultra-large files
2 None (bare repo) SSH
  • Can provide strong security
  • Supports ultra-large files
  • Supports external access
  • Setup can be simple or complex
3 GitHub Local (file)
  • External access is not required
  • Supports ultra-large files
  • Simplest setup if network is already set up
4 GitHub SSH
  • Can provide strong security
  • Supports ultra-large files
  • Supports external access
  • Setup can be simple or complex
5 GitHub 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

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

I am still working on this scenario.

I think maybe the problem I encountered below is because the protocol used when committing to LFS must be the same as was used when cloning.

Or maybe I committed the large files to Git instead of LFS.

Shell
$ git clone file://gojira/home/mslinn/bare_repo1 /tmp/asdf
Cloning into '/tmp/asdf'...
remote: Enumerating objects: 14, done.
remote: Counting objects: 100% (14/14), done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (14/14), done.
Downloading 200MB.zip (210 MB)
Error downloading object: 200MB.zip (d14b731): Smudge error: Error downloading 200MB.zip (d14b73150642f30d2342e6620fa537ea273a58b8b751fc5af8f4aabe809f8fc4): error transferring "d14b73150642f30d2342e6620fa537ea273a58b8b751fc5af8f4aabe809f8fc4": [0] remote missing object d14b73150642f30d2342e6620fa537ea273a58b8b751fc5af8f4aabe809f8fc4

Errors logged to '/tmp/asdf/.git/lfs/logs/20250117T144743.02793146.log'.
Use `git lfs logs last` to view the log.
error: external filter 'git-lfs filter-process' failed
fatal: 200MB.zip: smudge filter lfs failed
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry with 'git restore --source=HEAD :/' 
$ cd /tmp/asdf/
$ git status On branch master Your branch is up to date with 'origin/master'. Changes to be committed: (use "git restore --staged ..." to unstage) deleted: .gitattributes deleted: 200MB.zip deleted: BigBuckBunny_640x360.m4v deleted: big_buck_bunny_480p_h264.mov deleted: big_buck_bunny_480p_stereo.avi deleted: big_buck_bunny_480p_stereo.ogg deleted: big_buck_bunny_720p_h264.mov deleted: big_buck_bunny_720p_stereo.avi deleted: big_buck_bunny_720p_stereo.ogg deleted: enwik9.zip deleted: normal_file.txt deleted: rdf-files.tar.zip Untracked files: (use "git add ..." to include in what will be committed) .gitattributes normal_file.txt

Scenario 2: Remote Git Repository With Locally Accessible Large Files

Scenario Git Server Git LFS Protocol Use Cases
2 None (bare repo) 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/bare_repo1 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 bare_repo1 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 clone gojira:bare_repo1 Cloning into 'bare_repo1'... warning: You appear to have cloned an empty repository.
    mslinn@bear git_lfs $ cd bare_repo1
  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 bare_repo1 $ 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 bare_repo1 $ git config remote.origin.url
gojira:bare_repo1 
mslinn@bear bare_repo1 $ ls # No files at present
mslinn@bear bare_repo1 $ 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 bare_repo1 $ git lfs track $(wp 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 bare_repo1 $ 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 bare_repo1 Git repository.
Shell
mslinn@bear bare_repo1 $ echo 'Hello world' > normal_file.txt
mslinn@bear bare_repo1 $ time scp gojira: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 bare_repo1 $ 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 bare_repo1 $ 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 bare_repo1 $ vlc .git/lfs/objects/45/c8/45c8bafeb9a53df7f491198d2e71529701bcf1cd51805782089fac1d32869f9b

Commit

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

Shell
mslinn@bear bare_repo1 $ 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 bare_repo1 $ time git push -u origin master
Locking support detected on remote "origin". Consider enabling it with:
  $ git config lfs.https://gojira/home/mslinn/bare_repo1.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/bare_repo1
 * [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/bare_repo1/.git/lfs/objects. We can see the same IDs for the large files in both copies of the repository.

Client-side (bear)
mslinn@bear bare_repo1 $ 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 bare_repo1 $ ssh gojira tree bare_repo1/.git/lfs/objects
bare_repo1/.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

Scenario Git Server Git LFS Protocol Use Cases
3 GitHub Local (file)
  • 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

Scenario Git Server Git LFS Protocol Use Cases
4 GitHub SSH
  • Can provide strong security
  • Supports ultra-large files
  • Supports external access
  • Setup can be simple or complex
Scenario Git Server Git LFS Protocol Use Cases
5 GitHub 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 7 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-ls-files and Wildmatch
  5. Git LFS Filename Patterns & Tracking
  6. Git LFS Client Configuration & Commands
  7. Working With Git LFS
  8. Evaluation Procedure For Git LFS Servers
  9. 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.