Git-Annex from scratch#
I found I missed an under-the-hood explanation of what Git-Annex added on top of an ordinary Git repository.
I’m partly starting from Joey Hess’ talk.
Here is a set of fundamental objects for Git-Annex, from that talk:
Git-Annex objects (GAOs)#
GAOs are objects in the same sense that Git stores objects in its object store.
For Git (not Git-Annex), when we do git add myfile, Git takes the contents
of myfile (well, in fact, a compressed version of myfile) and copies it
into the .git/objects directory, with a filename generated from the SHA1 hash
of the object.
However, Git-Annex is designed to work with very large files that should not be
compressed or copied. So when you create a Git-Annex object (with git annex add mybigfile):
The file is moved, not copied.
Within the working tree, Git-Annex leaves behind a pointer (usually a symbolic link) to the now-moved file. See below.
The moved file is not compressed.
The contents go into
.git/annex/objectsinstead of.git/objects.Git-Annex removes write permission on the moved file, to prevent you overwriting the file accidentally (see lock/unlock).
There are different rules for making the hash filename that we don’t need to worry about for now.
Git-Annex pointers#
Above, we said that when we make a file into a Git-Annex object, we leave
a pointer to the now-moved file, in the working tree. The pointer is (on a Unix system) a symlink to the now-moved file in the .git/annex directory.
(Some systems, such as Windows by default, can’t use symbolic links; in this case Git-Annex can use pointer files).
It is these pointers (usually symbolic links) that we check into the Git repository.
Git-Annex remotes#
Git-Annex has remotes. Remotes are stores for GAOs.
This can be confusing, because:
Git-Annex can re-use ordinary Git repositories for this purpose. In this case the remote in the Git-Annex sense can also be a remote in the Git sense.
But Git-Annex also has the concept of a special remote, that is not a Git repository, and therefore cannot be a remote in the Git sense. Special remotes have various types of storage backing and interfaces, such as
rclonepointing to Google Drive and so on.
I’ll call both Git re-used repositories, and special remotes — Git-Annex Remotes (GARs) — because they have the specific purpose of storing the GAOs. GARs are pointers to particular data stores that can store GAOs.
Each GAR has a Universally Unique Identifier (UUID), so that Git-Annex can record which of its remotes has each of the potential GAOs.
As this implies, any one individual GAR need not store all possible GAOs - it
might have a subset. Git-Annex has to keep track of this information, so it
can git annex get any files that the user asks for, that they do not already
have in their .git/annex GAO object store.
Here is the confusion — your local Git repository also functions as a GAR — and
has its own UUID. That is because, on your machine, after you git annex add
(or, as you see later git annex get) a large file, your own local .git
directory will have a copy of the GAO — in .git/annex/objects. So Git-Annex
has to know that you also have a copy of the file, in your .git/annex/objects
directory, so you or your collaborators can ask for a copy of the file from
you, if they need to (using git annex get etc).
To say it again - a GAR is a different thing from a standard Git remote. It is
specifically a place that Git-Annex can store or retrieve GAOs. Your local
.git repository store is one such place, so it also functions as a GAR, from
which you, or other people connected to your network, can get GAOs.
Git-Annex metadata#
Git-Annex makes and uses its own branch named git-annex to store metadata
about the location of GAOs within the known remotes. We’ll come across that
branch in the walkthrough below.
Walkthrough#
Start with standard Git#
Make a new directory for the Git repository, that will soon also be Git-Annex-enhanced.
mkdir my-repo
cd my-repo
When we do git init we have the usual .git subdirectories — this is, so
far, a normal Git repository.
git init
ls .git
Initialized empty Git repository in /tmp/tmp_pf0j390/annex-repos/my-repo/.git/
HEAD config description hooks info objects refs
tree .git/objects
.git/objects
├── info
└── pack
3 directories, 0 files
echo "Some text" > my_small_file
# Makes my_small_file into a Git object, stored in .git/objects
git add my_small_file
tree .git/objects
.git/objects
├── 01
│ └── b51e6ac5e39d1eb2fec3143437c6f117a58f03
├── info
└── pack
4 directories, 1 file
Make an ordinary Git commit. This generates a new directory-listing object, and a new commit object — the usual Git behavior.[1]
git commit -m "Added first file"
tree .git/objects
[main (root-commit) fb62273] Added first file
1 file changed, 1 insertion(+)
create mode 100644 my_small_file
.git/objects
├── 01
│ └── b51e6ac5e39d1eb2fec3143437c6f117a58f03
├── 7b
│ └── 527389ed9d0c64db733e80fadb582e211b5b7c
├── fb
│ └── 62273823002a788458e21e398bc0e5a88406dc
├── info
└── pack
6 directories, 3 files
We only have one branch - the default main branch:
git branch -a
* main
Here’s the current (default) .git/config file. Remember, this file is local to the repository, and does not get pushed with a git push. Notice here there is no [annex] section.
cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
By default, Git doesn’t have a pre-commit file in hooks. We’ll see later that Git-Annex adds one.
ls .git/hooks
applypatch-msg.sample pre-commit.sample prepare-commit-msg.sample
commit-msg.sample pre-merge-commit.sample push-to-checkout.sample
fsmonitor-watchman.sample pre-push.sample sendemail-validate.sample
post-update.sample pre-rebase.sample update.sample
pre-applypatch.sample pre-receive.sample
Add Git-Annex features with git annex init#
Now we overlay the Git-Annex stuff on the normal Git repository:
git annex init
init ok
(recording state in git...)
Notice that we now have a new annex directory in the .git directory.
ls .git
COMMIT_EDITMSG annex description index logs refs
HEAD config hooks info objects
.git/annex doesn’t have any objects yet, just some housekeeping files.
tree .git/annex
.git/annex
├── fsck
│ ├── fsck.lck
│ └── fsckdb
│ └── db
├── index
├── index.lck
├── journal
├── journal.lck
├── mergedrefs
├── othertmp
├── othertmp.lck
├── sentinal
└── sentinal.cache
5 directories, 9 files
We also have a new git-annex branch:
git branch -a
git-annex
* main
Next we look at git annex info to show the Git-Annex-Remotes:
git annex info
trusted repositories: 0
semitrusted repositories: 3
00000000-0000-0000-0000-000000000001 -- web
00000000-0000-0000-0000-000000000002 -- bittorrent
93a51ec7-3dc0-4438-b553-0e76636779f4 -- runner@runnervmejwal:/tmp/tmp_pf0j390/annex-repos/my-repo [here]
untrusted repositories: 0
transfers in progress: none
available local disk space: 91.43 gigabytes (+100 megabytes reserved)
local annex keys: 0
local annex size: 0 bytes
annexed files in working tree: 0
size of annexed files in working tree: 0 bytes
combined annex size of all repositories: 0 bytes
annex sizes of repositories:
backend usage:
bloom filter size: 32 mebibytes (0% full)
For the moment, notice the “semitrusted repositories”. These are GARs - places
that Git-Annex knows can be sources or destinations for the Git-Annex-Object
files. The first two (web and bittorrent) are generic sources
corresponding to URLs, and bittorrent files, but we’ll ignore these for now.
The third points to the local .git directory. This is a “remote” in a rather
confusing sense - that is - it’s a place that can serve as a source for the
files that Git-Annex will store (the GAOs). It’s a remote in the sense that
other Git-Annex overlaid repositories may be able to use to get those files.
You can think of it as a remote to other repositories, and a local store to
this one.
After git annex init, the .git/config file has an [annex] section that
identifies this repository store (set of files on disk) with its new UUID.
cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[annex]
uuid = 93a51ec7-3dc0-4438-b553-0e76636779f4
version = 10
[filter "annex"]
smudge = git-annex smudge -- %f
clean = git-annex smudge --clean -- %f
process = git-annex filter-process
Git-Annex has added a pre-commit and other hooks to allow it to intercept and modify Git commands.
ls .git/hooks
applypatch-msg.sample post-update.sample pre-rebase.sample
commit-msg.sample pre-applypatch.sample pre-receive.sample
fsmonitor-watchman.sample pre-commit prepare-commit-msg.sample
post-checkout pre-commit.sample push-to-checkout.sample
post-merge pre-merge-commit.sample sendemail-validate.sample
post-receive pre-push.sample update.sample
Adding files with git annex add#
Now we’ll add some potentially large file, using git annex add. That is going to make a Git-Annex Object (GAO), and leave a pointer (symlink) behind in the working directory.
First we make a file that we will pretend is large. We’ll also calculate its SHA1 sum - you’ll see why later.
echo "Something really large" > my_large_file
shasum my_large_file
eee0481dabe361a026281b3260a37732fa79881d my_large_file
Now we make that large file into a Git-Annex Object with git annex add:
git annex add my_large_file
add my_large_file
100% 23 B 83 KiB/s 0s
ok
(recording state in git...)
Notice now that:
The file has moved to
.git/annex/objects.The moved file is now read-only (to prevent you accidentally overwriting it — see lock/unlock).
There’s a symlink to that file in the working directory.
Git-Annex added the symlink (and not the file itself) to the ordinary Git staging area.
In order:
# It's the same file exactly as the one previously in the working directory.
shasum .git/annex/objects/*/*/*/*
eee0481dabe361a026281b3260a37732fa79881d .git/annex/objects/4k/p7/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720
# The moved file is read-only to prevent accidental overwrites.
ls -al .git/annex/objects/*/*/*/*
-r--r--r-- 1 runner runner 23 Sep 5 16:27 .git/annex/objects/4k/p7/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720
# The file in the working tree has become a symlink.
ls -al my_large_file
lrwxrwxrwx 1 runner runner 180 Sep 5 16:27 my_large_file -> .git/annex/objects/4k/p7/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720
# The symlink has been added to the Git staging area.
git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: my_large_file
git annex list tells us which GAR has each GAO. here is the name for the local GAR, in .git/annex:
git annex list
here
|web
||bittorrent
|||
X__ my_large_file
We can now do a commit - but notice - the thing that gets added to the standard Git history, and stays in .git/objects, is nothing but the symlink:
git commit -m "Add link to my_large_file"
[main f41d3c7] Add link to my_large_file
1 file changed, 1 insertion(+)
create mode 120000 my_large_file
git show below identifies the symlink with the file mode 120000:
# Show contents of last commit.
git show main
commit f41d3c791defe41b528f43ba661c9681718f75f0 (HEAD -> main)
Author: ci <ci@example.com>
Date: Sat Sep 5 16:27:23 2026 +0000
Add link to my_large_file
diff --git a/my_large_file b/my_large_file
new file mode 120000
index 0000000..aa09c8c
--- /dev/null
+++ b/my_large_file
@@ -0,0 +1 @@
+.git/annex/objects/4k/p7/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720
\ No newline at end of file
To recap - the Git .git/objects directory never got a copy of
my_large_file. The content of my_large_file belongs to Git-Annex, and is
stored in the .git/annex/objects directory, that does not get transferred
when you git push, or you git clone the repository. Git itself only has
the symbolic link to the file. But as we’ll see soon, it does know where
copies of my_large_file live, through the information in the git-annex
branch.
Clones and Git-Annex#
To illustrate, if we do a typical clone of this my-repo repository, we do not
have the contents of my_large_file; we only have the symlink:
cd ..
# Simulate pushing to some upstream service such as Github.
git clone --bare my-repo my-repo-upstream.git
# Simulate cloning from there.
git clone my-repo-upstream.git my-repo-clone
cd my-repo-clone
Cloning into bare repository 'my-repo-upstream.git'...
done.
Cloning into 'my-repo-clone'...
done.
We have put ourselves into the usual situation, where we’ve done a git push
to a remote service, and then done a git clone from the remote service, to
another computer. After doing that:
# No .git/annex directory in the clone.
ls .git
HEAD config description hooks index info logs objects packed-refs refs
# But we do have access to the remote git-annex branch, which maps
# repositories to GAOs.
git branch -a
* main
remotes/origin/HEAD -> origin/main
remotes/origin/git-annex
remotes/origin/main
# my_large_file is a broken symlink.
file my_large_file
my_large_file: broken symbolic link to .git/annex/objects/4k/p7/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720
If we try and fetch the file to which the symlink points, with git annex get,
Git-Annex can’t do it, because it has no record (in the git-annex remote
branch) that the origin (upstream) repository has that file in its
.git/annex filesystem.
git annex list
here
|origin
||web
|||bittorrent
||||
____ my_large_file
Fetching files with git annex get#
However, the git-annex remote branch has told it where we might find the file
(see Git-Annex internals).
Notice the suggestion generated during the failure:
git annex get my_large_file
get my_large_file (not available)
Maybe add some of these git remotes (git remote add ...):
93a51ec7-3dc0-4438-b553-0e76636779f4 -- runner@runnervmejwal:/tmp/tmp_pf0j390/annex-repos/my-repo
failed
get: 1 failed
This message is telling us that we need to point directly to the original
repository as a source for the GAO files (in its .git/annex folder).
# Add the original repository as an ordinary Git remote.
git remote add original-repo ../my-repo --fetch
Updating original-repo
From ../my-repo
* [new branch] git-annex -> original-repo/git-annex
* [new branch] main -> original-repo/main
# Git-Annex now knows it can get the file.
git annex list
here
|origin
||original-repo
|||web
||||bittorrent
|||||
__X__ my_large_file
We ask Git-Annex to fetch the file.
git annex get my_large_file
get my_large_file (from original-repo...)
100% 23 B 10 KiB/s 0s
ok
(recording state in git...)
# There are two repositories that Git-Annex knows have
# the file (in `.git/annex`) - this repo, and the original.
git annex list
here
|origin
||original-repo
|||web
||||bittorrent
|||||
X_X__ my_large_file
# After git annex get, the symlink is fixed, because the file exists.
file my_large_file
my_large_file: symbolic link to .git/annex/objects/4k/p7/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720
Copying annex files with git annex copy#
# Make another pretend large file.
echo "More large" > another_large_file
# Calculate, store, show hash of file.
alf_hash=$(shasum another_large_file)
echo $alf_hash
9d1363628fdb6b44fe2c632d371ffb62d190dd6c another_large_file
# Add it to the Annex
git annex add another_large_file
# It's a symlink now.
ls -al another_large_file
add another_large_file
100% 11 B 49 KiB/s 0s
ok
(recording state in git...)
lrwxrwxrwx 1 runner runner 180 Sep 5 16:27 another_large_file -> .git/annex/objects/gQ/q1/SHA256E-s11--509997cd7487f362f7f72733f63c5b7bc9e73adfa9df45e53cbf749b6c69c6c3/SHA256E-s11--509997cd7487f362f7f72733f63c5b7bc9e73adfa9df45e53cbf749b6c69c6c3
git commit -m "Add another large file"
[main 7da91e9] Add another large file
1 file changed, 1 insertion(+)
create mode 120000 another_large_file
We have this file in my-repo-clone, but the original my-repo does not:
git annex list
here
|origin
||original-repo
|||web
||||bittorrent
|||||
X____ another_large_file
X_X__ my_large_file
# Notice we have the bytes of another_large_file
# (identified by the shasum, run above), as well as
# the original my_large_file
echo "SHA of another_large_file: $alf_hash"
echo "SHA of files in git annex objects directories:"
shasum .git/annex/objects/*/*/*/*
SHA of another_large_file: 9d1363628fdb6b44fe2c632d371ffb62d190dd6c another_large_file
SHA of files in git annex objects directories:
eee0481dabe361a026281b3260a37732fa79881d .git/annex/objects/4k/p7/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720
9d1363628fdb6b44fe2c632d371ffb62d190dd6c .git/annex/objects/gQ/q1/SHA256E-s11--509997cd7487f362f7f72733f63c5b7bc9e73adfa9df45e53cbf749b6c69c6c3/SHA256E-s11--509997cd7487f362f7f72733f63c5b7bc9e73adfa9df45e53cbf749b6c69c6c3
We can send our file to the original my-repo:
# Not there at the moment:
shasum ../my-repo/.git/annex/objects/*/*/*/*
eee0481dabe361a026281b3260a37732fa79881d ../my-repo/.git/annex/objects/4k/p7/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720
git annex copy another_large_file --to original-repo
copy another_large_file (to original-repo...)
100% 11 B 5 KiB/s 0s
ok
(recording state in git...)
# It arrived.
shasum ../my-repo/.git/annex/objects/*/*/*/*
eee0481dabe361a026281b3260a37732fa79881d ../my-repo/.git/annex/objects/4k/p7/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720/SHA256E-s23--5533fe49b4ab945dc8c4b9d878a8c6e74232c8e00fe9a2b2e5f8d88a70406720
9d1363628fdb6b44fe2c632d371ffb62d190dd6c ../my-repo/.git/annex/objects/gQ/q1/SHA256E-s11--509997cd7487f362f7f72733f63c5b7bc9e73adfa9df45e53cbf749b6c69c6c3/SHA256E-s11--509997cd7487f362f7f72733f63c5b7bc9e73adfa9df45e53cbf749b6c69c6c3
The awe-inspiring power of git annex sync#
Let’s add another file.
echo "More larger still" > yet_another_large_file
git annex add yet_another_large_file
git commit -m "Add yet another large file"
add yet_another_large_file
100% 18 B 64 KiB/s 0s
ok
(recording state in git...)
[main b53b9d6] Add yet another large file
1 file changed, 1 insertion(+)
create mode 120000 yet_another_large_file
# This repo is the only one with this file.
git annex list
here
|origin
||original-repo
|||web
||||bittorrent
|||||
X_X__ another_large_file
X_X__ my_large_file
X____ yet_another_large_file
# The current known git branches
git fetch original-repo # In fact this has not changed.
git branch -av
remote: Enumerating objects: 6, done.
remote: Counting objects: 16% (1/6)
remote: Counting objects: 33% (2/6)
remote: Counting objects: 50% (3/6)
remote: Counting objects: 66% (4/6)
remote: Counting objects: 83% (5/6)
remote: Counting objects: 100% (6/6)
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 25% (1/4)
remote: Compressing objects: 50% (2/4)
remote: Compressing objects: 75% (3/4)
remote: Compressing objects: 100% (4/4)
remote: Compressing objects: 100% (4/4), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 20% (1/5)
Unpacking objects: 40% (2/5)
Unpacking objects: 60% (3/5)
Unpacking objects: 80% (4/5)
Unpacking objects: 100% (5/5)
Unpacking objects: 100% (5/5), 472 bytes | 472.00 KiB/s, done.
From ../my-repo
72bbc39..826b9d4 git-annex -> original-repo/git-annex
git-annex ab27826 update
* main b53b9d6 [ahead 2] Add yet another large file
remotes/origin/HEAD -> origin/main
remotes/origin/git-annex 72bbc39 update
remotes/origin/main f41d3c7 Add link to my_large_file
remotes/original-repo/HEAD -> original-repo/main
remotes/original-repo/git-annex 826b9d4 update
remotes/original-repo/main f41d3c7 Add link to my_large_file
I’m now going to run git annex sync without qualification. This does a rather extreme Git and Git-Annex synchronization of this repository with the other repositories known to Git-Annex.
Read the linked documentation page. Usually, with Git, you do not push directly to remotes, other than a single source-of-truth remote, and that remote is typically a --bare remote - it does not have a working tree. If it does have a working tree, Git will, by default, warn you about this and decline, because it is so easy for the branch to get out of sync with the working tree.
However, Git-Annex sync, by default, takes a different approach - as it is so common to want to keep both Git history and the Git-Annex files in sync between repositories.
As you’ll see in the linked documentation, by default, Git-Annex applies a synchronization scheme devised by Joachim Breitner, involving a special set of branches with names starting synced/. As we’re currently on the main branch, the corresponding synced/ branch is synced/main.
In particular git annex sync, by default, does the following:
Automatically commits any changes in the working tree (configure with the
annex.autocommitsetting, see below).Merges the
synced/mainbranch (in our case) (if we have one) intomain. This pulls in any Git history and files that a previousgit annex syncpushed into our repository. In our case, we do not have such a branch, because no-one has done agit annex syncto us.Fetches from each remote, and merges in any changes from other remotes.
Pushes (in our case)
maindirectly to any remotes. Where Git prevents pushing to a branch with a working tree, it instead pushes tosynced/main(in our case).Copies any annexed files that the remote repository “wants”. “Wanting” is something you need to configure per repository. We haven’t done that yet, so no content gets copied
git annex sync
(merging original-repo/git-annex into git-annex...)
(recording state in git...)
commit
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
ok
pull original-repo
ok
pull origin
remote: Enumerating objects: 5, done.
remote: Counting objects: 20% (1/5)
remote: Counting objects: 40% (2/5)
remote: Counting objects: 60% (3/5)
remote: Counting objects: 80% (4/5)
remote: Counting objects: 100% (5/5)
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 33% (1/3)
remote: Compressing objects: 66% (2/3)
remote: Compressing objects: 100% (3/3)
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 33% (1/3)
Unpacking objects: 66% (2/3)
Unpacking objects: 100% (3/3)
Unpacking objects: 100% (3/3), 383 bytes | 383.00 KiB/s, done.
From /tmp/tmp_pf0j390/annex-repos/my-repo-upstream
72bbc39..3639ecb git-annex -> origin/git-annex
ok
(merging origin/git-annex into git-annex...)
(recording state in git...)
push original-repo
Enumerating objects: 50, done.
Counting objects: 2% (1/46)
Counting objects: 4% (2/46)
Counting objects: 6% (3/46)
Counting objects: 8% (4/46)
Counting objects: 10% (5/46)
Counting objects: 13% (6/46)
Counting objects: 15% (7/46)
Counting objects: 17% (8/46)
Counting objects: 19% (9/46)
Counting objects: 21% (10/46)
Counting objects: 23% (11/46)
Counting objects: 26% (12/46)
Counting objects: 28% (13/46)
Counting objects: 30% (14/46)
Counting objects: 32% (15/46)
Counting objects: 34% (16/46)
Counting objects: 36% (17/46)
Counting objects: 39% (18/46)
Counting objects: 41% (19/46)
Counting objects: 43% (20/46)
Counting objects: 45% (21/46)
Counting objects: 47% (22/46)
Counting objects: 50% (23/46)
Counting objects: 52% (24/46)
Counting objects: 54% (25/46)
Counting objects: 56% (26/46)
Counting objects: 58% (27/46)
Counting objects: 60% (28/46)
Counting objects: 63% (29/46)
Counting objects: 65% (30/46)
Counting objects: 67% (31/46)
Counting objects: 69% (32/46)
Counting objects: 71% (33/46)
Counting objects: 73% (34/46)
Counting objects: 76% (35/46)
Counting objects: 78% (36/46)
Counting objects: 80% (37/46)
Counting objects: 82% (38/46)
Counting objects: 84% (39/46)
Counting objects: 86% (40/46)
Counting objects: 89% (41/46)
Counting objects: 91% (42/46)
Counting objects: 93% (43/46)
Counting objects: 95% (44/46)
Counting objects: 97% (45/46)
Counting objects: 100% (46/46)
Counting objects: 100% (46/46), done.
Delta compression using up to 4 threads
Compressing objects: 3% (1/32)
Compressing objects: 6% (2/32)
Compressing objects: 9% (3/32)
Compressing objects: 12% (4/32)
Compressing objects: 15% (5/32)
Compressing objects: 18% (6/32)
Compressing objects: 21% (7/32)
Compressing objects: 25% (8/32)
Compressing objects: 28% (9/32)
Compressing objects: 31% (10/32)
Compressing objects: 34% (11/32)
Compressing objects: 37% (12/32)
Compressing objects: 40% (13/32)
Compressing objects: 43% (14/32)
Compressing objects: 46% (15/32)
Compressing objects: 50% (16/32)
Compressing objects: 53% (17/32)
Compressing objects: 56% (18/32)
Compressing objects: 59% (19/32)
Compressing objects: 62% (20/32)
Compressing objects: 65% (21/32)
Compressing objects: 68% (22/32)
Compressing objects: 71% (23/32)
Compressing objects: 75% (24/32)
Compressing objects: 78% (25/32)
Compressing objects: 81% (26/32)
Compressing objects: 84% (27/32)
Compressing objects: 87% (28/32)
Compressing objects: 90% (29/32)
Compressing objects: 93% (30/32)
Compressing objects: 96% (31/32)
Compressing objects: 100% (32/32)
Compressing objects: 100% (32/32), done.
Writing objects: 2% (1/36)
Writing objects: 5% (2/36)
Writing objects: 8% (3/36)
Writing objects: 11% (4/36)
Writing objects: 13% (5/36)
Writing objects: 16% (6/36)
Writing objects: 19% (7/36)
Writing objects: 22% (8/36)
Writing objects: 25% (9/36)
Writing objects: 27% (10/36)
Writing objects: 30% (11/36)
Writing objects: 33% (12/36)
Writing objects: 36% (13/36)
Writing objects: 38% (14/36)
Writing objects: 41% (15/36)
Writing objects: 44% (16/36)
Writing objects: 47% (17/36)
Writing objects: 50% (18/36)
Writing objects: 52% (19/36)
Writing objects: 55% (20/36)
Writing objects: 58% (21/36)
Writing objects: 61% (22/36)
Writing objects: 63% (23/36)
Writing objects: 66% (24/36)
Writing objects: 69% (25/36)
Writing objects: 72% (26/36)
Writing objects: 75% (27/36)
Writing objects: 77% (28/36)
Writing objects: 80% (29/36)
Writing objects: 83% (30/36)
Writing objects: 86% (31/36)
Writing objects: 88% (32/36)
Writing objects: 91% (33/36)
Writing objects: 94% (34/36)
Writing objects: 97% (35/36)
Writing objects: 100% (36/36)
Writing objects: 100% (36/36), 3.25 KiB | 831.00 KiB/s, done.
Total 36 (delta 9), reused 0 (delta 0), pack-reused 0 (from 0)
To ../my-repo
* [new branch] main -> synced/main
* [new branch] git-annex -> synced/git-annex
ok
push origin
Enumerating objects: 49, done.
Counting objects: 2% (1/46)
Counting objects: 4% (2/46)
Counting objects: 6% (3/46)
Counting objects: 8% (4/46)
Counting objects: 10% (5/46)
Counting objects: 13% (6/46)
Counting objects: 15% (7/46)
Counting objects: 17% (8/46)
Counting objects: 19% (9/46)
Counting objects: 21% (10/46)
Counting objects: 23% (11/46)
Counting objects: 26% (12/46)
Counting objects: 28% (13/46)
Counting objects: 30% (14/46)
Counting objects: 32% (15/46)
Counting objects: 34% (16/46)
Counting objects: 36% (17/46)
Counting objects: 39% (18/46)
Counting objects: 41% (19/46)
Counting objects: 43% (20/46)
Counting objects: 45% (21/46)
Counting objects: 47% (22/46)
Counting objects: 50% (23/46)
Counting objects: 52% (24/46)
Counting objects: 54% (25/46)
Counting objects: 56% (26/46)
Counting objects: 58% (27/46)
Counting objects: 60% (28/46)
Counting objects: 63% (29/46)
Counting objects: 65% (30/46)
Counting objects: 67% (31/46)
Counting objects: 69% (32/46)
Counting objects: 71% (33/46)
Counting objects: 73% (34/46)
Counting objects: 76% (35/46)
Counting objects: 78% (36/46)
Counting objects: 80% (37/46)
Counting objects: 82% (38/46)
Counting objects: 84% (39/46)
Counting objects: 86% (40/46)
Counting objects: 89% (41/46)
Counting objects: 91% (42/46)
Counting objects: 93% (43/46)
Counting objects: 95% (44/46)
Counting objects: 97% (45/46)
Counting objects: 100% (46/46)
Counting objects: 100% (46/46), done.
Delta compression using up to 4 threads
Compressing objects: 3% (1/33)
Compressing objects: 6% (2/33)
Compressing objects: 9% (3/33)
Compressing objects: 12% (4/33)
Compressing objects: 15% (5/33)
Compressing objects: 18% (6/33)
Compressing objects: 21% (7/33)
Compressing objects: 24% (8/33)
Compressing objects: 27% (9/33)
Compressing objects: 30% (10/33)
Compressing objects: 33% (11/33)
Compressing objects: 36% (12/33)
Compressing objects: 39% (13/33)
Compressing objects: 42% (14/33)
Compressing objects: 45% (15/33)
Compressing objects: 48% (16/33)
Compressing objects: 51% (17/33)
Compressing objects: 54% (18/33)
Compressing objects: 57% (19/33)
Compressing objects: 60% (20/33)
Compressing objects: 63% (21/33)
Compressing objects: 66% (22/33)
Compressing objects: 69% (23/33)
Compressing objects: 72% (24/33)
Compressing objects: 75% (25/33)
Compressing objects: 78% (26/33)
Compressing objects: 81% (27/33)
Compressing objects: 84% (28/33)
Compressing objects: 87% (29/33)
Compressing objects: 90% (30/33)
Compressing objects: 93% (31/33)
Compressing objects: 96% (32/33)
Compressing objects: 100% (33/33)
Compressing objects: 100% (33/33), done.
Writing objects: 2% (1/38)
Writing objects: 5% (2/38)
Writing objects: 7% (3/38)
Writing objects: 10% (4/38)
Writing objects: 13% (5/38)
Writing objects: 15% (6/38)
Writing objects: 18% (7/38)
Writing objects: 21% (8/38)
Writing objects: 23% (9/38)
Writing objects: 26% (10/38)
Writing objects: 28% (11/38)
Writing objects: 31% (12/38)
Writing objects: 34% (13/38)
Writing objects: 36% (14/38)
Writing objects: 39% (15/38)
Writing objects: 42% (16/38)
Writing objects: 44% (17/38)
Writing objects: 47% (18/38)
Writing objects: 50% (19/38)
Writing objects: 52% (20/38)
Writing objects: 55% (21/38)
Writing objects: 57% (22/38)
Writing objects: 60% (23/38)
Writing objects: 63% (24/38)
Writing objects: 65% (25/38)
Writing objects: 68% (26/38)
Writing objects: 71% (27/38)
Writing objects: 73% (28/38)
Writing objects: 76% (29/38)
Writing objects: 78% (30/38)
Writing objects: 81% (31/38)
Writing objects: 84% (32/38)
Writing objects: 86% (33/38)
Writing objects: 89% (34/38)
Writing objects: 92% (35/38)
Writing objects: 94% (36/38)
Writing objects: 97% (37/38)
Writing objects: 100% (38/38)
Writing objects: 100% (38/38), 3.37 KiB | 861.00 KiB/s, done.
Total 38 (delta 9), reused 0 (delta 0), pack-reused 0 (from 0)
To /tmp/tmp_pf0j390/annex-repos/my-repo-upstream.git
* [new branch] main -> synced/main
* [new branch] git-annex -> synced/git-annex
ok
We note, and then ignore, that the repos to which we have synced now have synced/git-annex branches. This is not important for our purposes.
Notice too that the original repo (but not the upstream origin repo) has
a new branch synced/main, because here, as is the default, Git did not allow
a push directly to the current branch of a non-bare repository (a repository
with a working tree).
git branch -av
git-annex 48c6f6a merging origin/git-annex into git-annex
* main b53b9d6 Add yet another large file
synced/main b53b9d6 Add yet another large file
remotes/origin/HEAD -> origin/main
remotes/origin/git-annex 48c6f6a merging origin/git-annex into git-annex
remotes/origin/main b53b9d6 Add yet another large file
remotes/origin/synced/git-annex 48c6f6a merging origin/git-annex into git-annex
remotes/origin/synced/main b53b9d6 Add yet another large file
remotes/original-repo/HEAD -> original-repo/main
remotes/original-repo/git-annex 48c6f6a merging origin/git-annex into git-annex
remotes/original-repo/main f41d3c7 Add link to my_large_file
remotes/original-repo/synced/git-annex 48c6f6a merging origin/git-annex into git-annex
remotes/original-repo/synced/main b53b9d6 Add yet another large file
In our case, sync does not copy the annex content to the original repository, because we haven’t told Git-Annex that that remote “wants” these files yet.
git annex list
here
|origin
||original-repo
|||web
||||bittorrent
|||||
X_X__ another_large_file
X_X__ my_large_file
X____ yet_another_large_file
Let’s configure the wanted setting for the original repository:
git annex wanted original-repo "include=*_large_file"
wanted original-repo ok
(recording state in git...)
Re-run git annex sync, now Git-Annex knows the file is wanted:
git annex sync
git annex list
git-annex sync will change default behavior in the future to send content to repositories that have preferred content configured. If you do not want this to send any content, use --no-content (or -g) to prepare for that change. (Or you can configure annex.synccontent)
commit
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
ok
pull original-repo
ok
pull origin
ok
push original-repo
Enumerating objects: 4, done.
Counting objects: 25% (1/4)
Counting objects: 50% (2/4)
Counting objects: 75% (3/4)
Counting objects: 100% (4/4)
Counting objects: 100% (4/4), done.
Delta compression using up to 4 threads
Compressing objects: 33% (1/3)
Compressing objects: 66% (2/3)
Compressing objects: 100% (3/3)
Compressing objects: 100% (3/3), done.
Writing objects: 33% (1/3)
Writing objects: 66% (2/3)
Writing objects: 100% (3/3)
Writing objects: 100% (3/3), 429 bytes | 429.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To ../my-repo
48c6f6a..4f4865f git-annex -> synced/git-annex
ok
push origin
Enumerating objects: 4, done.
Counting objects: 25% (1/4)
Counting objects: 50% (2/4)
Counting objects: 75% (3/4)
Counting objects: 100% (4/4)
Counting objects: 100% (4/4), done.
Delta compression using up to 4 threads
Compressing objects: 33% (1/3)
Compressing objects: 66% (2/3)
Compressing objects: 100% (3/3)
Compressing objects: 100% (3/3), done.
Writing objects: 33% (1/3)
Writing objects: 66% (2/3)
Writing objects: 100% (3/3)
Writing objects: 100% (3/3), 429 bytes | 429.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To /tmp/tmp_pf0j390/annex-repos/my-repo-upstream.git
48c6f6a..4f4865f git-annex -> synced/git-annex
ok
here
|origin
||original-repo
|||web
||||bittorrent
|||||
X_X__ another_large_file
X_X__ my_large_file
X____ yet_another_large_file
You might want to constrain git annex sync#
First, you might have noted the first step in the sync above; git annex sync, by default, automatically does a commit of any changes to the working tree before it starts.
We make some staged and unstaged changes to the working tree:
# Show the last commit
git log -1
commit b53b9d6491c7800f0120c5cb427b277abdce35bb (HEAD -> main, original-repo/synced/main, origin/synced/main, origin/main, origin/HEAD, synced/main)
Author: ci <ci@example.com>
Date: Sat Sep 5 16:27:27 2026 +0000
Add yet another large file
# Make a new file
echo "A file" > a_file
# Stage it.
git add a_file
# Make some more unstaged changes to file.
echo "Another line" >> a_file
git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: a_file
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: a_file
Now:
git annex sync
git-annex sync will change default behavior in the future to send content to repositories that have preferred content configured. If you do not want this to send any content, use --no-content (or -g) to prepare for that change. (Or you can configure annex.synccontent)
commit
[main 2e739a7] git-annex in runner@runnervmejwal:/tmp/tmp_pf0j390/annex-repos/my-repo-clone
1 file changed, 2 insertions(+)
create mode 100644 a_file
ok
pull original-repo
ok
pull origin
ok
push original-repo
Enumerating objects: 4, done.
Counting objects: 25% (1/4)
Counting objects: 50% (2/4)
Counting objects: 75% (3/4)
Counting objects: 100% (4/4)
Counting objects: 100% (4/4), done.
Delta compression using up to 4 threads
Compressing objects: 50% (1/2)
Compressing objects: 100% (2/2)
Compressing objects: 100% (2/2), done.
Writing objects: 33% (1/3)
Writing objects: 66% (2/3)
Writing objects: 100% (3/3)
Writing objects: 100% (3/3), 323 bytes | 323.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
To ../my-repo
b53b9d6..2e739a7 main -> synced/main
ok
push origin
Enumerating objects: 4, done.
Counting objects: 25% (1/4)
Counting objects: 50% (2/4)
Counting objects: 75% (3/4)
Counting objects: 100% (4/4)
Counting objects: 100% (4/4), done.
Delta compression using up to 4 threads
Compressing objects: 50% (1/2)
Compressing objects: 100% (2/2)
Compressing objects: 100% (2/2), done.
Writing objects: 33% (1/3)
Writing objects: 66% (2/3)
Writing objects: 100% (3/3)
Writing objects: 100% (3/3), 323 bytes | 323.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
To /tmp/tmp_pf0j390/annex-repos/my-repo-upstream.git
b53b9d6..2e739a7 main -> synced/main
ok
# Sync committed the staged and unstaged changes.
git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
# We have a new automated commit.
git log -1
commit 2e739a7da803e87bec17f53050e0ee2d38530aa9 (HEAD -> main, original-repo/synced/main, origin/synced/main, origin/main, origin/HEAD, synced/main)
Author: ci <ci@example.com>
Date: Sat Sep 5 16:27:30 2026 +0000
git-annex in runner@runnervmejwal:/tmp/tmp_pf0j390/annex-repos/my-repo-clone
You will see that sync made a new commit for you. You might want that, but I do not, and I turn it off thus:
# Don't do automatic worktree commits.
git annex config --set annex.autocommit false
annex.autocommit false ok
(recording state in git...)
Now back to the merges. As you can imagine, whenever you push directly into
another branch, with a history that is not fast-forward, you can get merge
conflicts. That means that, if you are not careful, git annex sync will
generate merge conflicts, either pulling into our repo, or pushing into
another. As usual, merge conflicts can be obscure and difficult to resolve.
These forced merges also make it harder to think about what Git is doing, and
for many of us, that makes the process more obscure. Combining the possibility
of merge conflicts with Git-Annex as an extra layer on top of Git, makes things
even more difficult.
Therefore, I suggest that you, like me, tell Git-Annex not to apply this synced merge strategy, and instead, do Git pushes manually.
We make another commit just for the illustration:
echo "Yet another another" > yet_yet_another_large_file
git annex add yet_yet_another_large_file
git commit -m "Another another large file"
add yet_yet_another_large_file
100% 20 B 81 KiB/s 0s
ok
(recording state in git...)
[main 81b577a] Another another large file
1 file changed, 1 insertion(+)
create mode 120000 yet_yet_another_large_file
You can turn off Git-Annex’ synced merge behavior for your repository with:
# Sync content only, not Git branches.
git annex config --set annex.synconlyannex true
annex.synconlyannex true ok
(recording state in git...)
Now:
git annex sync
git-annex sync will change default behavior in the future to send content to repositories that have preferred content configured. If you do not want this to send any content, use --no-content (or -g) to prepare for that change. (Or you can configure annex.synccontent)
pull original-repo
From ../my-repo
* branch git-annex -> FETCH_HEAD
* branch synced/git-annex -> FETCH_HEAD
ok
pull origin
From /tmp/tmp_pf0j390/annex-repos/my-repo-upstream
* branch git-annex -> FETCH_HEAD
* branch synced/git-annex -> FETCH_HEAD
ok
copy another_large_file (to origin...)
100% 11 B 5 KiB/s 0s
ok
copy my_large_file (to origin...)
100% 23 B 237 KiB/s 0s
ok
copy yet_another_large_file (to original-repo...)
100% 18 B 9 KiB/s 0s
ok
copy yet_another_large_file (to origin...)
100% 18 B 195 KiB/s 0s
ok
copy yet_yet_another_large_file (to original-repo...)
100% 20 B 244 KiB/s 0s
ok
copy yet_yet_another_large_file (to origin...)
100% 20 B 167 KiB/s 0s
ok
pull original-repo
From ../my-repo
* branch git-annex -> FETCH_HEAD
* branch synced/git-annex -> FETCH_HEAD
ok
pull origin
From /tmp/tmp_pf0j390/annex-repos/my-repo-upstream
* branch git-annex -> FETCH_HEAD
* branch synced/git-annex -> FETCH_HEAD
ok
(recording state in git...)
push original-repo
Enumerating objects: 35, done.
Counting objects: 2% (1/35)
Counting objects: 5% (2/35)
Counting objects: 8% (3/35)
Counting objects: 11% (4/35)
Counting objects: 14% (5/35)
Counting objects: 17% (6/35)
Counting objects: 20% (7/35)
Counting objects: 22% (8/35)
Counting objects: 25% (9/35)
Counting objects: 28% (10/35)
Counting objects: 31% (11/35)
Counting objects: 34% (12/35)
Counting objects: 37% (13/35)
Counting objects: 40% (14/35)
Counting objects: 42% (15/35)
Counting objects: 45% (16/35)
Counting objects: 48% (17/35)
Counting objects: 51% (18/35)
Counting objects: 54% (19/35)
Counting objects: 57% (20/35)
Counting objects: 60% (21/35)
Counting objects: 62% (22/35)
Counting objects: 65% (23/35)
Counting objects: 68% (24/35)
Counting objects: 71% (25/35)
Counting objects: 74% (26/35)
Counting objects: 77% (27/35)
Counting objects: 80% (28/35)
Counting objects: 82% (29/35)
Counting objects: 85% (30/35)
Counting objects: 88% (31/35)
Counting objects: 91% (32/35)
Counting objects: 94% (33/35)
Counting objects: 97% (34/35)
Counting objects: 100% (35/35)
Counting objects: 100% (35/35), done.
Delta compression using up to 4 threads
Compressing objects: 5% (1/19)
Compressing objects: 10% (2/19)
Compressing objects: 15% (3/19)
Compressing objects: 21% (4/19)
Compressing objects: 26% (5/19)
Compressing objects: 31% (6/19)
Compressing objects: 36% (7/19)
Compressing objects: 42% (8/19)
Compressing objects: 47% (9/19)
Compressing objects: 52% (10/19)
Compressing objects: 57% (11/19)
Compressing objects: 63% (12/19)
Compressing objects: 68% (13/19)
Compressing objects: 73% (14/19)
Compressing objects: 78% (15/19)
Compressing objects: 84% (16/19)
Compressing objects: 89% (17/19)
Compressing objects: 94% (18/19)
Compressing objects: 100% (19/19)
Compressing objects: 100% (19/19), done.
Writing objects: 4% (1/25)
Writing objects: 8% (2/25)
Writing objects: 12% (3/25)
Writing objects: 16% (4/25)
Writing objects: 20% (5/25)
Writing objects: 24% (6/25)
Writing objects: 28% (7/25)
Writing objects: 36% (9/25)
Writing objects: 40% (10/25)
Writing objects: 44% (11/25)
Writing objects: 48% (12/25)
Writing objects: 52% (13/25)
Writing objects: 56% (14/25)
Writing objects: 60% (15/25)
Writing objects: 64% (16/25)
Writing objects: 68% (17/25)
Writing objects: 72% (18/25)
Writing objects: 76% (19/25)
Writing objects: 80% (20/25)
Writing objects: 84% (21/25)
Writing objects: 88% (22/25)
Writing objects: 92% (23/25)
Writing objects: 96% (24/25)
Writing objects: 100% (25/25)
Writing objects: 100% (25/25), 1.82 KiB | 929.00 KiB/s, done.
Total 25 (delta 10), reused 0 (delta 0), pack-reused 0 (from 0)
To ../my-repo
4f4865f..649903b git-annex -> synced/git-annex
ok
push origin
Enumerating objects: 35, done.
Counting objects: 2% (1/35)
Counting objects: 5% (2/35)
Counting objects: 8% (3/35)
Counting objects: 11% (4/35)
Counting objects: 14% (5/35)
Counting objects: 17% (6/35)
Counting objects: 20% (7/35)
Counting objects: 22% (8/35)
Counting objects: 25% (9/35)
Counting objects: 28% (10/35)
Counting objects: 31% (11/35)
Counting objects: 34% (12/35)
Counting objects: 37% (13/35)
Counting objects: 40% (14/35)
Counting objects: 42% (15/35)
Counting objects: 45% (16/35)
Counting objects: 48% (17/35)
Counting objects: 51% (18/35)
Counting objects: 54% (19/35)
Counting objects: 57% (20/35)
Counting objects: 60% (21/35)
Counting objects: 62% (22/35)
Counting objects: 65% (23/35)
Counting objects: 68% (24/35)
Counting objects: 71% (25/35)
Counting objects: 74% (26/35)
Counting objects: 77% (27/35)
Counting objects: 80% (28/35)
Counting objects: 82% (29/35)
Counting objects: 85% (30/35)
Counting objects: 88% (31/35)
Counting objects: 91% (32/35)
Counting objects: 94% (33/35)
Counting objects: 97% (34/35)
Counting objects: 100% (35/35)
Counting objects: 100% (35/35), done.
Delta compression using up to 4 threads
Compressing objects: 5% (1/19)
Compressing objects: 10% (2/19)
Compressing objects: 15% (3/19)
Compressing objects: 21% (4/19)
Compressing objects: 26% (5/19)
Compressing objects: 31% (6/19)
Compressing objects: 36% (7/19)
Compressing objects: 42% (8/19)
Compressing objects: 47% (9/19)
Compressing objects: 52% (10/19)
Compressing objects: 57% (11/19)
Compressing objects: 63% (12/19)
Compressing objects: 68% (13/19)
Compressing objects: 73% (14/19)
Compressing objects: 78% (15/19)
Compressing objects: 84% (16/19)
Compressing objects: 89% (17/19)
Compressing objects: 94% (18/19)
Compressing objects: 100% (19/19)
Compressing objects: 100% (19/19), done.
Writing objects: 4% (1/25)
Writing objects: 8% (2/25)
Writing objects: 12% (3/25)
Writing objects: 16% (4/25)
Writing objects: 20% (5/25)
Writing objects: 24% (6/25)
Writing objects: 28% (7/25)
Writing objects: 36% (9/25)
Writing objects: 40% (10/25)
Writing objects: 44% (11/25)
Writing objects: 48% (12/25)
Writing objects: 52% (13/25)
Writing objects: 56% (14/25)
Writing objects: 60% (15/25)
Writing objects: 64% (16/25)
Writing objects: 68% (17/25)
Writing objects: 72% (18/25)
Writing objects: 76% (19/25)
Writing objects: 80% (20/25)
Writing objects: 84% (21/25)
Writing objects: 88% (22/25)
Writing objects: 92% (23/25)
Writing objects: 96% (24/25)
Writing objects: 100% (25/25)
Writing objects: 100% (25/25), 1.82 KiB | 929.00 KiB/s, done.
Total 25 (delta 10), reused 0 (delta 0), pack-reused 0 (from 0)
To /tmp/tmp_pf0j390/annex-repos/my-repo-upstream.git
4f4865f..649903b git-annex -> synced/git-annex
ok
Notice that the commit didn’t get sent to the remotes, but the large file did:
git branch -av
git-annex 649903b update
* main 81b577a [ahead 1] Another another large file
synced/main 2e739a7 git-annex in runner@runnervmejwal:/tmp/tmp_pf0j390/annex-repos/my-repo-clone
remotes/origin/HEAD -> origin/main
remotes/origin/git-annex 649903b update
remotes/origin/main 2e739a7 git-annex in runner@runnervmejwal:/tmp/tmp_pf0j390/annex-repos/my-repo-clone
remotes/origin/synced/git-annex 649903b update
remotes/origin/synced/main 2e739a7 git-annex in runner@runnervmejwal:/tmp/tmp_pf0j390/annex-repos/my-repo-clone
remotes/original-repo/HEAD -> original-repo/main
remotes/original-repo/git-annex 649903b update
remotes/original-repo/main f41d3c7 Add link to my_large_file
remotes/original-repo/synced/git-annex 649903b update
remotes/original-repo/synced/main 2e739a7 git-annex in runner@runnervmejwal:/tmp/tmp_pf0j390/annex-repos/my-repo-clone
git annex list
here
|origin
||original-repo
|||web
||||bittorrent
|||||
XXX__ another_large_file
XXX__ my_large_file
XXX__ yet_another_large_file
XXX__ yet_yet_another_large_file
We send the commit manually:
git push origin main
Enumerating objects: 4, done.
Counting objects: 25% (1/4)
Counting objects: 50% (2/4)
Counting objects: 75% (3/4)
Counting objects: 100% (4/4)
Counting objects: 100% (4/4), done.
Delta compression using up to 4 threads
Compressing objects: 33% (1/3)
Compressing objects: 66% (2/3)
Compressing objects: 100% (3/3)
Compressing objects: 100% (3/3), done.
Writing objects: 33% (1/3)
Writing objects: 66% (2/3)
Writing objects: 100% (3/3)
Writing objects: 100% (3/3), 377 bytes | 377.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
To /tmp/tmp_pf0j390/annex-repos/my-repo-upstream.git
2e739a7..81b577a main -> main
The special remotes#
As you have seen, Git-Annex can use Git repositories on filesystems as storage
for GAOs — in the repository .git/annex directory.
But Git-Annex can also have special remotes, that have no necessary relationship to Git repositories. For example, they can work with directories on file-sharing systems such as Dropbox and Google Drive.
You create special remotes with the git annex initremote command.
I say special remotes have no necessary relationship to Git repositories, but you can also label a Git repository as a special remote, using the --type git flag to git annex initremote. Read the note below for more detail.
Note
By the way, I hate to be confusing, but you will have seen above (with
../my-repo), that Git-Annex can identify repositories as capable of storing
annex files — a sort of special-remote-in-practice — and it can do this when it
has some way to read the .git/config of the repository, as it can, for
example when the repository is on the local file-system. It uses .git/config
to work out what the UUID is of the remote repository. Adding the repository
explicitly as a special remote, with git annex initrepo --type git, stores
the UUID in the git-annex branch, along with some useful location (such as
SSH path, filesystem path, etc). When other repositories clone or otherwise
update their git-annex branch with this information, they can see the stored
UUID, and therefore use that remote as a Git-Annex special remote.
You can enable known and already-configured special remotes with git annex enableremote <remotename>. This tells the local repository that you want to
be able to use that remote to fetch and store annex files, with sync and
other commands.
I don’t cover special remotes further here, but see the Git-Annex page on special remotes, and the doc page on special remotes.
Which files go where#
Consider setting annex.largefiles entries with git annex config, or in your
.gitattributes file, to tell Git-Annex which files it should handle (as GAOs)
and which Git should handle. See the annex.largefiles
page for details. Note that
annex.largefiles just identifies files that Git-Annex should handle. You can
use values for annex.largefiles to make Git-Annex always operate on files
larger than a particular size, but you can also use that setting to configure
Git / Git-Annex to select files by path name.
Consider using git annex wanted commands (see above) to tell Git-Annex which
remotes should house which files. These rules get stored in the git-annex
branch. See the Git-Annex wanted
page for more.
Whither Git-Annex#
That was a tour of the basics. You might now want to have a look at the primary Git-Annex pages.