git-annex/ design/ new repo versions

This page's purpose is to collect and explore plans for a future annex.version.

There are two major possible changes that could go in a new repo version that would require a hard migration of git-annex repositories:

1. Changing .git/annex/objects/ paths, as appear in the git-annex symlinks.

2. Changing the layout of the git-annex branch in a substantial way.

## object path changes

Any change in this area requires the user make changes to their master branch, any other active branches. Old un-converted tags and other historical trees in git would also be broken. This is a pretty bad user experience. (And it bloats history with a commit that rewrites everything too.

For this reason, any changes in this area have been avoided, going all the way back to v2 (2011).

> git-annex had approximately 3 users at the time of that migration, and as one of them, I can say it was a total PITA. --Joey

So, there would need to be significant payoffs to justify this change.

Note that changing the hash directories might also change where objects are stored in special remotes. Because repos can be offline or expensive to migrate (or both -- Glacier!) any such changes need to keep looking in the old locations for backwards compatability.

Possible reasons to make changes:

- It's annoyingly inconsistent that git-annex uses a different hash directory layout for non-bare repository (on a non-crippled filesystem) than is used for bare repositories and some special remotes.

  Users occasionally stumble over this difference when messing with internals. The code is somewhat complicated by it. In some cases, git-annex checks both locations (eg, a bare repo defaults to xxx/yyy but really old ones might use xX/yY for some keys).

  The mixed case hash directories have caused trouble on case-insensitive filesystems, although that has mostly been papered over to avoid problems. One remaining problem users can stumble on occurs when moving a repository from OSX to Linux.

- The hash directories, and also the per-key directories can slow down using a repository on a disk (both SSD and spinning).

  https://github.com/datalad/datalad/issues/32

  Initial benchmarks suggest that going from xX/yY/KEY/OBJ to xX/yY/OBJ directories would improve speed 3x.

  Presumably, removing the yY would also speed it up, unless there are too many objects and the filesystem gets slow w/o the hash directories.

- Removing a directory level would also reduce disk usage, see scalability with lots of files for more info.

## git-annex branch changes

This might involve, eg, rethinking the xxx/yyy/ hash directories used in the git-annex branch.

Would this require a hard version transition? It might be possible to avoid one, but then git-annex would have to look in both the old and the new place. And if a un-transitioned repo was merged into a transitioned one, git-annex would have to look in both places, and union merge the two sets of data on the fly. This doubles the git-cat-file overhead of every operation involving the git-annex branch. So a hard transition would probably be best.

Also, note that w/o a hard transition, there's the risk that a old git-annex version gets ahold of a git-annex branch created by a new git-annex version, and sees only half of the story (the un-transitioned files). This could be a very confusing failure mode. It doesn't help that the git-annex branch does not currently have any kind of version number embedded in it, so the old version of git-annex doesn't even have a way to check if it can handle the branch.

Possible reasons to make changes:

- There is a discussion of some possible changes to the hash directories here https://github.com/datalad/datalad/issues/17#issuecomment-68558319 with a goal of reducing the overhead of the git-annex branch in the overall size of the git-annex repository.

  Removing the second-level hash directories might improve performance. It doesn't save much space when a repository is having incremental changes made to it. However, if millions of annexed objects are being added in a single commit, removing the second-level hash directories does save space; it halves the number of tree objects1.

  Also, https://github.com/datalad/datalad/issues/17#issuecomment-68569727 suggests using xxx/yyy.log, where one log contains information for multiple keys. This would probably improve performance too due to caching, although in some cases git-annex would have to process extra information to get to the info about the key it wants, which hurts performance. The disk usage change of this method has not yet been quantified.

- While not a sufficient reason on its own, the best practices for file formats in the git-annex branch has evolved over time, and there are some files that have unusual formats for historical reasons. Other files have modern formats, but their parsers have to cope with old versions that have other formats. A hard transition would provide an opportunity to clean up a lot of that.

## living on the edge

Rather than a hard transition, git-annex could add a mode that could be optionally enabled when initing a repo for the first time.

Users who know they need that mode could then turn it one, and get the benefits, while everyone else avoids a transition that doesn't benefit them much.

There could even be multiple modes, with different tradeoffs depending on how the repo will be used, its size, etc. Of course that adds complexity.

But the main problem with this idea is, how to avoid the foot shooting result of merging repo A(v5) into repo B(vNG)? This seems like it would be all to easy for a user to do.

As far as git-annex branch changes go, it might be possible for git-annex to paper over the problem by handling both versions in the merged git-annex branch, as discussed earlier. But for .git/annex/objects/ changes, there does not seem to be a reasonable thing for git-annex to do. When it's receiving an object into a mixed v5 and vNG repo, it can't know which location that repo expects the object file to be located in. Different files in the repo might point to the same object in different locations! Total mess. Must avoid this.

Currently, annex.version is a per-local-repo setting. git-annex can't tell if two repos that it's merging have different annex.version's.

It would be possible to add a git-annex:version file, which would work for git-annex branch merging. Ie, `git-annex merge` could detect if different git-annex branches have different versions, and refuse to merge them (or upgrade the old one before merging it).

Also, that file could be used by git-annex, to automatically set annex.version when auto-initing a clone of a repo that was initted with a newer than default version.

But git-anex:version won't prevent merging B/master into A's master. That merge can be done by git; nothing in git-annex can prevent it.

What we could do is have a .annex-version flag file in the root of the repo. Then git merge would at least have a merge conflict. Note that this means inflicting the file on all git-annex repos, even ones used by people with no intention of living on the edge. And, it would take quite a while until all such repos get updated to contain such a file.

Or, we could just document that if you initialize a repo with experimental annex.version, you're living on the edge and you can screw up your repo by merging with a repo from an old version.

git-annex fsck could also fix up any broken links that do result from the inevitable cases where users ignore the docs.

## version numbers vs configuration

A particular annex.version like 5 encompasses a number of somewhat distinct things

- git-annex branch layout
- .git/annex/objects/ layout
- other git stuff (like eg, the name of the HEAD branch in direct mode)

If the user is specifying at `git annex init` time some nonstandard things they want to make the default meet their use case better, that is more a matter of configuration than of picking a version.

For example, we could say that the user is opting out of the second-level object hash directories. Or we could say the user is choosing to use vNG, which is like v5 except with different object hash directory structure.

    git annex init --config annex.objects.hashdirectories 1
        --config annex.objects.hashlower true
    git annex init --version 6

The former would be more flexible. The latter is simpler.

The former also lets the user chose no hash directories, or choose 2 levels of hash directories while using the (v5 default) mixed case hashing.

## concrete design

Make git-annex:difference.log be used by newer git-annex versions than v5, and by nonstandard configurations.

The file contents will be "timestamp uuid [value, ..]", where value is a serialized data type that describes divergence from v5 (since v5 and older don't have the git-annex:difference.log file).

So, for example, "[Version 6]" could indicate that v6 is being used. Or, "[ObjectHashLower True, ObjectHashDirectories 1, BranchHashDirectories 1]" indicate a nonstandard configuration on top of v5 (this might turn out to be identical to v6; just make the compare equal and no problem).

git-annex merge would check if it's merging in a git-annex:difference.log from another repo that doesn't match the git-annex:difference.log of the local repo, and abort. git-annex sync (and the assistant) would check the same, but before merging master branches either, to avoid a bad merge there.

The git-annex:difference.log of a local repo could be changed by an upgrade or some sort of transition. When this happens, the new value is written for the uuid of the local repo. git-annex merge would then refuse to merge with remote repos until they were also transitioned.

(There's perhaps some overlap here with the existing git-annex:transitions.log, however the current transitions involve forgetting history/dead remotes and so can be done repeatedly on a repository. Also, the current transitions can be performed on remote branches before merging them in; that wouldn't work well for version changes since those require other changes in the remote repo.)

Not covered:

- git-merge of other branches, such as master (can be fixed by `git annex fix` or `fsck`)
- Old versions of git-annex will ignore the version file of course, and so merging such repos using them can result in pain.

comment 2

"... merging repo A(v5) into repo B(v6)? This seems like it would be all to easy for a user to do."

it would be easy if two separate repos are inited separately of different versions and then merged. But how frequent could such situation arise? I don't think that it is not that common actually in non git-annex assistant initialized repositories, especially if "v6" (or some other custom) layout would be non-default.

This also somewhat reflects upon "When it's receiving an object into a mixed v5 and v6 repo, it can't know which location that repo expects the object file to be located in." -- how plausible/frequent would be to see a mixed v5/6 repo?

".annex-version flag file" -- I think it is a good idea. (may be even better an .annex/version directory/file to reserve for possibly future other branch-specific, thus not for git-annex branch additions). Indeed it would take time for repos to acquire it, but imho it is ok, if added automagically by new git-annex versions. Also it is a tiny price to pay for users who do not really care about "living on the edge". But it also might better be coupled with having git-annex:version (i.e. version file under git-annex branch) describing layout of the git-annex branch since those two are somewhat independent, right?

Also a wild idea to mitigate inconvenience for users happen they migrate repositories from old to new formats: to provide 'git annex compat-layout [version]' command which, for files with content, would generate symlinks to files in new format to locations in old, with some command to clean them up later on (e.g. 'git annex compat-layout --cleanup'). Although inefficient per-se it could be a big convenience since would eliminate need to "migrate/rewrite" entire history, while still making it possible to get access to previous versions. With a handling in hooks could be automated to even hide that from users entirely. or is there a big culprit I don't see?

"version numbers vs configuration"

having flexibility of configurations, possibly with versions simply providing enumerations to some of them, might be a great feature to have in the long run. Not sure though if it would not blow up complexity :-/

Overall -- if complete transition to improved/unified v6 is too big of undertaker, allowing for custom non-default configurations while sticking to v5 as the default one and forbidding merges between different versions, could be sufficient to scale up for atypical large uses. If everyone to migrate to v6 -- more optimal layout should be well thought through to be worthwhile undertaking.

Comment by https://www.google.com/accounts/o8/id?id=AItOawnx8kHW66N3BqmkVpgtXDlYMvr8TJ5VvfY — Tue Jul 22 18:11:47 2025
comment 1

My gut feeling for incompatible changes in v6 is to somehow make v5 and below error out on all merges with a v6 repo.

Conversely, v6 git-annex running in v6 repos would transition all data while merging in v5 repos.

As a migration path for existing repos, you could move `git-annex` and `master` to `git-annex_v5` and `master_v5`. Once you are certain all known repos within that repo group are transitioned, either kill the v5 branches automagically or print info on cli/web UI on what needs to be run to get rid of them.

Having several plain git 1+ GiB repos (i.e. without any annex objects), I would love to transition after v6 and the migration paths have been hung a month or three to dry.

Comment by https://www.google.com/accounts/o8/id?id=AItOawl9sYlePmv1xK-VvjBdN-5doOa_Xw-jH4U — Tue Jul 22 18:11:47 2025
Comments on this page are closed.