https://github.com/python/cpython/commit/15c9d9027ef5090e58db1da21a95d11cdb5cd0a9 commit: 15c9d9027ef5090e58db1da21a95d11cdb5cd0a9 branch: main author: Stan Ulbrych <[email protected]> committer: hugovk <[email protected]> date: 2025-12-15T12:16:56+02:00 summary:
gh-141081: Add a `.gitignore` file to `__pycache__` folders (#141162) Co-authored-by: Hugo van Kemenade <[email protected]> Co-authored-by: Brett Cannon <[email protected]> files: A Misc/NEWS.d/next/Library/2025-11-06-17-37-51.gh-issue-141081.NJtULs.rst M Doc/whatsnew/3.15.rst M Lib/importlib/_bootstrap_external.py M Lib/py_compile.py M Lib/test/test_compileall.py M Lib/test/test_importlib/source/test_file_loader.py M Lib/test/test_py_compile.py diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index a94486dd4805bd..d9a34fe920d10d 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -74,6 +74,8 @@ Summary -- Release highlights * :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object <whatsnew315-pep782>` * :ref:`Improved error messages <whatsnew315-improved-error-messages>` +* :ref:`__pycache__ directories now contain a .gitignore file + <whatsnew315-pycache-gitignore>` New features @@ -397,6 +399,12 @@ Other language changes for any class. (Contributed by Serhiy Storchaka in :gh:`41779`.) +.. _whatsnew315-pycache-gitignore: + +* :file:`__pycache__` directories now contain a :file:`.gitignore` file for Git + that ignores their contents. + (Contributed by Stan Ulbrych in :gh:`141081`.) + New modules =========== diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index b576ceb1ce9f6e..a3089de4705f73 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -967,6 +967,19 @@ def set_data(self, path, data, *, _mode=0o666): _bootstrap._verbose_message('could not create {!r}: {!r}', parent, exc) return + + if part == _PYCACHE: + gitignore = _path_join(parent, '.gitignore') + try: + _path_stat(gitignore) + except FileNotFoundError: + gitignore_content = b'# Created by CPython\n*\n' + try: + _write_atomic(gitignore, gitignore_content, _mode) + except OSError: + pass + except OSError: + pass try: _write_atomic(path, data, _mode) _bootstrap._verbose_message('created {!r}', path) diff --git a/Lib/py_compile.py b/Lib/py_compile.py index 43d8ec90ffb6b1..b8324e7256a566 100644 --- a/Lib/py_compile.py +++ b/Lib/py_compile.py @@ -155,6 +155,14 @@ def compile(file, cfile=None, dfile=None, doraise=False, optimize=-1, dirname = os.path.dirname(cfile) if dirname: os.makedirs(dirname) + if os.path.basename(dirname) == '__pycache__': + gitignore = os.path.join(dirname, '.gitignore') + if not os.path.exists(gitignore): + try: + with open(gitignore, 'wb') as f: + f.write(b'# Created by CPython\n*\n') + except OSError: + pass except FileExistsError: pass if invalidation_mode == PycInvalidationMode.TIMESTAMP: diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py index 8384c183dd92dd..c7c44299c5a829 100644 --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py @@ -625,8 +625,10 @@ def f(self, ext=ext, switch=switch): ['-m', 'compileall', '-q', self.pkgdir])) # Verify the __pycache__ directory contents. self.assertTrue(os.path.exists(self.pkgdir_cachedir)) - expected = sorted(base.format(sys.implementation.cache_tag, ext) - for base in ('__init__.{}.{}', 'bar.{}.{}')) + expected = ['.gitignore'] + sorted( + base.format(sys.implementation.cache_tag, ext) + for base in ('__init__.{}.{}', 'bar.{}.{}') + ) self.assertEqual(sorted(os.listdir(self.pkgdir_cachedir)), expected) # Make sure there are no .pyc files in the source directory. self.assertFalse([fn for fn in os.listdir(self.pkgdir) diff --git a/Lib/test/test_importlib/source/test_file_loader.py b/Lib/test/test_importlib/source/test_file_loader.py index 5d5d4722171a8e..5e88f0dbed081e 100644 --- a/Lib/test/test_importlib/source/test_file_loader.py +++ b/Lib/test/test_importlib/source/test_file_loader.py @@ -180,6 +180,21 @@ def test_overridden_unchecked_hash_based_pyc(self): data[8:16], ) + @util.writes_bytecode_files + def test_gitignore_in_pycache(self): + with util.create_modules('_temp') as mapping: + source = mapping['_temp'] + loader = self.machinery.SourceFileLoader('_temp', source) + mod = types.ModuleType('_temp') + mod.__spec__ = self.util.spec_from_loader('_temp', loader) + loader.exec_module(mod) + pyc = os.path.dirname(self.util.cache_from_source(source)) + gitignore = os.path.join(pyc, '.gitignore') + self.assertTrue(os.path.exists(gitignore)) + with open(gitignore, 'rb') as f: + t = f.read() + self.assertEqual(t, b'# Created by CPython\n*\n') + (Frozen_SimpleTest, Source_SimpleTest diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py index 64387296e84621..fdfb124c051884 100644 --- a/Lib/test/test_py_compile.py +++ b/Lib/test/test_py_compile.py @@ -207,6 +207,16 @@ def test_quiet(self): with self.assertRaises(py_compile.PyCompileError): py_compile.compile(bad_coding, doraise=True, quiet=1) + def test_gitignore_created(self): + py_compile.compile(self.source_path) + self.assertTrue(os.path.exists(self.cache_path)) + pyc = os.path.dirname(self.cache_path) + gitignore = os.path.join(pyc, '.gitignore') + self.assertTrue(os.path.exists(gitignore)) + with open(gitignore, 'rb') as f: + text = f.read() + self.assertEqual(text, b'# Created by CPython\n*\n') + class PyCompileTestsWithSourceEpoch(PyCompileTestsBase, unittest.TestCase, diff --git a/Misc/NEWS.d/next/Library/2025-11-06-17-37-51.gh-issue-141081.NJtULs.rst b/Misc/NEWS.d/next/Library/2025-11-06-17-37-51.gh-issue-141081.NJtULs.rst new file mode 100644 index 00000000000000..2b64f68f4dfd28 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-11-06-17-37-51.gh-issue-141081.NJtULs.rst @@ -0,0 +1,2 @@ +When ``__pycache__`` directories are created, they now contain a +``.gitignore`` file that ignores their contents. _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
