Repository navigation
Out-of-tree builds always report a "-dirty" git tag in sys.version #159440
Description
Bug report
sys.version embeds the output of git describe --all --always --dirty, which is compiled into the interpreter via -DGITTAG (Modules/getbuildinfo.o recipe in Makefile.pre.in). When CPython is built out-of-tree, that tag is always reported as -dirty, even when the source checkout has no local modifications, because configure.ac passes --git-dir without --work-tree.
Reproduction
$ git -C ~/git/cpython status --short # clean checkout, no output
$ mkdir -p ~/build/cpython && cd ~/build/cpython
$ bash ~/git/cpython/configure --prefix=$HOME/opt/cpython > /dev/null
$ make -j && make install -j
$ ~/opt/cpython/bin/python3 -VV
Python 3.16.0a1+dev (heads/main-dirty:25883627946, Oct 14 2026, 22:17:06) [GCC 11.4.0]
^^^^^The -dirty suffix is bogus: the checkout is at 2588362 with an unmodified work tree.
Root cause
configure.ac defines the git commands used for the build stamp:
GITVERSION="git --git-dir \$(srcdir)/.git rev-parse --short HEAD"
GITTAG="git --git-dir \$(srcdir)/.git describe --all --always --dirty"
GITBRANCH="git --git-dir \$(srcdir)/.git name-rev --name-only HEAD"--git-dir only says where the repository is; it does not set the work tree, which therefore defaults to the current directory. In an out-of-tree build, make runs in the build directory, so the internal diff-index HEAD that implements git describe --dirty resolves every tracked path against the build directory, where none of them exist. Every tracked file is treated as deleted, and the tree is reported as dirty.
Run from the build directory against a clean source tree:
$ cd ~/build/cpython
$ git --git-dir=$HOME/git/cpython/.git diff-index --name-only HEAD | wc -l
5490 # all tracked files appear deleted
$ git --git-dir=$HOME/git/cpython/.git describe --all --always --dirty
heads/main-dirty
$ git --git-dir=$HOME/git/cpython/.git --work-tree=$HOME/git/cpython describe --all --always --dirty
heads/main
$ strings ~/build/cpython/Modules/getbuildinfo.o | grep heads/
heads/main-dirty # the string that ends up in the binary
GITVERSION (rev-parse --short HEAD) and GITBRANCH (name-rev --name-only HEAD) are unaffected because they never touch the work tree — only the --dirty check is. In-tree builds are unaffected too, which is presumably why this went unnoticed.
Proposed fix
Pass the work tree explicitly (equivalently, run git with -C $(srcdir)):
GITVERSION="git --git-dir \$(srcdir)/.git --work-tree \$(srcdir) rev-parse --short HEAD"
GITTAG="git --git-dir \$(srcdir)/.git --work-tree \$(srcdir) describe --all --always --dirty"
GITBRANCH="git --git-dir \$(srcdir)/.git --work-tree \$(srcdir) name-rev --name-only HEAD"The suffix keeps its intended meaning: local modifications to the source tree are still reported, while an out-of-tree build of a clean checkout now reports a clean tag. In-tree builds are byte-for-byte unaffected since $(srcdir) is ..
Workaround
Until this is fixed, an out-of-tree build can be nudged into reporting the right tag by overriding GITTAG and forcing Modules/getbuildinfo.o to be recompiled (it does not depend on Makefile, so reconfiguring alone will not rebuild it):
$ rm -f Modules/getbuildinfo.o
$ make -j GITTAG="git --git-dir=$PWD/../cpython/.git --work-tree=$PWD/../cpython describe --all --always --dirty"
$ make installAffected versions
3.7 and later: --dirty was added by bpo-27593 (554626a, 2017-03-20) and first shipped in 3.7.0a1. Only out-of-tree builds are affected.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Activity
pythongh-159440: Pass --work-tree to the git commands used for the ve…
pythongh-159440: Pass --work-tree to the git commands used for the ve…
pythongh-159440: Pass --work-tree to the git commands used for the ve…
gh-159440: Pass --work-tree to the git commands used for the version …
[3.14] gh-159440: Pass --work-tree to the git commands used for the v…
[3.15] gh-159440: Pass --work-tree to the git commands used for the v…
sendaoYan commentedon Oct 15, 2026
Fixed.