Potential Race Condition Fix - OS Rename & Chmod - PermissionError by DEKHTIARJonathan · Pull Request #115 · gitpython-developers/gitdb

There is a really flaky and hard to debug bug when using gitdb at least on MacOS (M-Processor) using Docker (docker.io/python:3.13 image).

I have only been able to reproduce this error within pytest on my laptop (Apple M4 laptop).

I completely randomly get:

repo = Repo(........)  # Repo Created using `GitPython`
[...]
repo.index.add("*")

    ~~~~~~~~~~~~~~^^^^^
  File "/usr/local/lib/python3.13/site-packages/git/index/base.py", line 885, in add
    entries_added.extend(self._entries_for_paths(paths, path_rewriter, fprogress, entries))
                         ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/git/util.py", line 176, in wrapper
    return func(self, *args, **kwargs)
  File "/usr/local/lib/python3.13/site-packages/git/index/util.py", line 111, in set_git_working_dir
    return func(self, *args, **kwargs)
  File "/usr/local/lib/python3.13/site-packages/git/index/base.py", line 745, in _entries_for_paths
    entries_added.append(self._store_path(filepath, fprogress))
                         ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/git/index/base.py", line 698, in _store_path
    istream = self.repo.odb.store(IStream(Blob.type, st.st_size, stream))
  File "/usr/local/lib/python3.13/site-packages/gitdb/db/loose.py", line 233, in store
    chmod(obj_path, self.new_objects_mode)
    ~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
PermissionError: [Errno 1] Operation not permitted: '/git_projects/<GIT_PROJECT_NAME>/.git/objects/9d/961f54d04e15cef0e525d1cb3d937bd7b82b04'

When you inspect a little bit that obj_path - everything looks absolutely fine as far as I can tell. I have no idea what could trigger this PermissionError. The script is executing under the user dev-user:dev-user of UID/GID: 1000:1000

os.getuid()=1000
pwd.getpwuid(os.getuid())=pwd.struct_passwd(pw_name='dev-user', pw_passwd='x', pw_uid=1000, pw_gid=1000, pw_gecos='', pw_dir='/home/dev-user', pw_shell='/bin/bash')
os.stat(obj_path)=os.stat_result(st_mode=33152, st_ino=3713822, st_dev=65025, st_nlink=1, st_uid=1000, st_gid=1000, st_size=473, st_atime=1734989354, st_mtime=1734989354, st_ctime=1734989354)
Path(obj_path).exists()=True
Path(obj_path).owner()='dev-user'
Path(obj_path).group()='dev-user'

So clearly something is not going well.

Looking a little bit more in detail, turns out the error is always happening in scenario B - but not systematically in scenario B:

if isfile(obj_path):
    remove(tmp_path)
    print("SCENARIO A !!!!!!!!!!!!!!!")
else:
    rename(tmp_path, obj_path)
    print("SCENARIO B !!!!!!!!!!!!!!!")

There might be an interference between tempfile.mkstemp, os.rename and os.chmod. Not sure which one.

It seems like a retry loop with a small temporization seems to mitigate the problem.

One supposition is that os.rename might not entirely complete by the time it returns. Or some file descriptors might be corrupted. Unfortunately, it's really really hard for me to debug and I'm not sure even what to look for.