Description
When a repo backed by git-remote-s3 is added as a Git submodule, git lfs pull (and the implicit checkout that follows git submodule add) fails with:
error transferring "<oid>": [2] [Errno 20] Not a directory: '<worktree>/<sub>/.git/lfs/tmp/<oid>.<suffix>'
Failed to fetch some objects from 'https://s3///<profile>@<bucket>/<prefix>.git/info/lfs'
In a submodule, <sub>/.git is not a directory — it is a gitlink file containing gitdir: ../../.git/modules/<path> pointing to the real git directory in the parent repo. Two locations in git_remote_s3/lfs.py build paths through .git/... as a literal string, which is invalid in this context.
Reproduction
- Create two S3-backed repos (call them
parent and child) using the s3:// URL scheme.
- In
child, track a file with Git LFS, commit, and push. Confirm git lfs ls-files lists it.
- In
parent:
git submodule add s3://<profile>@<bucket>/<child-prefix> sub
- Observe the bundle clone succeed (
Unbundling objects: 100%), then the LFS materialization fail with [Errno 20] Not a directory. The submodule is left half-checked-out.
Manually running git lfs pull inside the submodule reproduces the same error after configuring the standalone transfer agent in the submodule's git config.
Root cause
In git_remote_s3/lfs.py:
- L19 — module-level
logging.basicConfig(filename=".git/lfs/tmp/git-lfs-s3.log") resolves .git/ against CWD. In a submodule this points at a regular file, not a directory.
- L115 —
LFSProcess.download() does temp_dir = os.path.abspath(".git/lfs/tmp") and writes {temp_dir}/{oid} (L118). Same problem.
Non-submodule clones work because .git happens to be a directory there. The git-lfs custom-transfer protocol does not include a path field in download events (per docs/custom-transfers.md), so the agent must construct the path itself — but it should use the resolved gitdir, not the literal string .git/.
Suggested fix
Resolve the gitdir via git rev-parse --absolute-git-dir at runtime:
def _git_dir() -> str:
return subprocess.check_output(
["git", "rev-parse", "--absolute-git-dir"], text=True
).strip()
Then in download():
temp_dir = os.path.join(_git_dir(), "lfs", "tmp")
os.makedirs(temp_dir, exist_ok=True)
For the L19 log path, defer FileHandler setup into main() so the resolution happens after CWD/init is settled — or use the same _git_dir() helper. Optionally, honoring lfs.storage (per git-lfs config docs) would let users override the location explicitly.
Environment
git-remote-s3: 0.3.2 (PyPI)
git: 2.39.5
git-lfs: 3.7.1 (GitHub; linux arm64; go 1.24.4)
- Python 3.11
- Linux 6.1 (aarch64)
Workaround
Patch the two hardcoded paths in the installed git_remote_s3/lfs.py to use git rev-parse --absolute-git-dir instead of the literal .git/.
Description
When a repo backed by
git-remote-s3is added as a Git submodule,git lfs pull(and the implicit checkout that followsgit submodule add) fails with:In a submodule,
<sub>/.gitis not a directory — it is a gitlink file containinggitdir: ../../.git/modules/<path>pointing to the real git directory in the parent repo. Two locations ingit_remote_s3/lfs.pybuild paths through.git/...as a literal string, which is invalid in this context.Reproduction
parentandchild) using thes3://URL scheme.child, track a file with Git LFS, commit, and push. Confirmgit lfs ls-fileslists it.parent:Unbundling objects: 100%), then the LFS materialization fail with[Errno 20] Not a directory. The submodule is left half-checked-out.Manually running
git lfs pullinside the submodule reproduces the same error after configuring the standalone transfer agent in the submodule's git config.Root cause
In
git_remote_s3/lfs.py:logging.basicConfig(filename=".git/lfs/tmp/git-lfs-s3.log")resolves.git/against CWD. In a submodule this points at a regular file, not a directory.LFSProcess.download()doestemp_dir = os.path.abspath(".git/lfs/tmp")and writes{temp_dir}/{oid}(L118). Same problem.Non-submodule clones work because
.githappens to be a directory there. The git-lfs custom-transfer protocol does not include a path field in download events (perdocs/custom-transfers.md), so the agent must construct the path itself — but it should use the resolved gitdir, not the literal string.git/.Suggested fix
Resolve the gitdir via
git rev-parse --absolute-git-dirat runtime:Then in
download():For the L19 log path, defer FileHandler setup into
main()so the resolution happens after CWD/init is settled — or use the same_git_dir()helper. Optionally, honoringlfs.storage(per git-lfs config docs) would let users override the location explicitly.Environment
git-remote-s3: 0.3.2 (PyPI)git: 2.39.5git-lfs: 3.7.1 (GitHub; linux arm64; go 1.24.4)Workaround
Patch the two hardcoded paths in the installed
git_remote_s3/lfs.pyto usegit rev-parse --absolute-git-dirinstead of the literal.git/.