Skip to content

Instantly share code, notes, and snippets.

@EliahKagan
Last active January 5, 2025 05:31
Show Gist options
  • Save EliahKagan/20042f202d94e3af5733082abcac2182 to your computer and use it in GitHub Desktop.
Save EliahKagan/20042f202d94e3af5733082abcac2182 to your computer and use it in GitHub Desktop.
GitPython test run using an MSYS2 build of Python 3.12
ek@Glub MSYS ~/repos-msys2
$ git clone [email protected]:EliahKagan/GitPython.git
Cloning into 'GitPython'...
remote: Enumerating objects: 25285, done.
remote: Counting objects: 100% (125/125), done.
remote: Compressing objects: 100% (57/57), done.
remote: Total 25285 (delta 89), reused 90 (delta 66), pack-reused 25160 (from 2)
Receiving objects: 100% (25285/25285), 10.10 MiB | 1.40 MiB/s, done.
Resolving deltas: 100% (16963/16963), done.
Updating files: 100% (208/208), done.
ek@Glub MSYS ~/repos-msys2
$ cd GitPython/
ek@Glub MSYS ~/repos-msys2/GitPython
$ git fetch
ek@Glub MSYS ~/repos-msys2/GitPython
$ ls
AUTHORS doc LICENSE release-verification-key.asc test
build-release.sh FUNDING.json Makefile requirements.txt test-requirements.txt
CHANGES fuzzing MANIFEST.in requirements-dev.txt tox.ini
check-version.sh git pyproject.toml SECURITY.md VERSION
CONTRIBUTING.md init-tests-after-clone.sh README.md setup.py
ek@Glub MSYS ~/repos-msys2/GitPython
$ git remote -v
origin [email protected]:EliahKagan/GitPython.git (fetch)
origin [email protected]:EliahKagan/GitPython.git (push)
ek@Glub MSYS ~/repos-msys2/GitPython
$ git remote add upstream [email protected]:gitpython-developers/GitPython.git
ek@Glub MSYS ~/repos-msys2/GitPython
$ git remote add kip ssh://team-roach.ddns.net/repos/GitPython.git
ek@Glub MSYS ~/repos-msys2/GitPython
$ git fetch --all --prune
Fetching origin
Fetching upstream
remote: Enumerating objects: 1566, done.
remote: Counting objects: 100% (441/441), done.
remote: Total 1566 (delta 440), reused 441 (delta 440), pack-reused 1125 (from 1)
Receiving objects: 100% (1566/1566), 919.19 KiB | 1.58 MiB/s, done.
Resolving deltas: 100% (1011/1011), completed with 116 local objects.
From github.com:gitpython-developers/GitPython
* [new branch] Fix-#1334 -> upstream/Fix-#1334
* [new branch] black-fmt -> upstream/black-fmt
* [new branch] experiment-2012 -> upstream/experiment-2012
* [new branch] fix -> upstream/fix
* [new branch] fix-1103 -> upstream/fix-1103
* [new branch] fix-ci-tests -> upstream/fix-ci-tests
* [new branch] fix-non-ascii-chars-in-status-lines -> upstream/fix-non-ascii-chars-in-status-lines
* [new branch] issue-232-reproduction -> upstream/issue-232-reproduction
* [new branch] issue-301-reproduction -> upstream/issue-301-reproduction
* [new branch] main -> upstream/main
* [new branch] master -> upstream/master
* [new branch] more-robust-git-diff -> upstream/more-robust-git-diff
* [new branch] no_devnull_open -> upstream/no_devnull_open
* [new branch] py2 -> upstream/py2
* [new branch] revert-357-autointerrupt_deadlock_fix -> upstream/revert-357-autointerrupt_deadlock_fix
* [new branch] typing -> upstream/typing
* [new tag] 3.1.41 -> 3.1.41
* [new tag] 3.1.42 -> 3.1.42
* [new tag] 3.1.43 -> 3.1.43
* [new tag] 3.1.44 -> 3.1.44
Fetching kip
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (4/4), done.
remote: Total 9 (delta 4), reused 4 (delta 4), pack-reused 5 (from 1)
Unpacking objects: 100% (9/9), 13.15 KiB | 70.00 KiB/s, done.
From ssh://team-roach.ddns.net/repos/GitPython
* [new branch] main -> kip/main
* [new branch] sanitize -> kip/sanitize
* [new branch] sanitize-next -> kip/sanitize-next
ek@Glub MSYS ~/repos-msys2/GitPython
$ ls
AUTHORS doc LICENSE release-verification-key.asc test
build-release.sh FUNDING.json Makefile requirements.txt test-requirements.txt
CHANGES fuzzing MANIFEST.in requirements-dev.txt tox.ini
check-version.sh git pyproject.toml SECURITY.md VERSION
CONTRIBUTING.md init-tests-after-clone.sh README.md setup.py
ek@Glub MSYS ~/repos-msys2/GitPython
$ ls -l
total 73
-rw-r--r-- 1 ek None 2334 Jan 4 22:40 AUTHORS
-rwxr-xr-x 1 ek None 1014 Jan 4 22:40 build-release.sh
-rw-r--r-- 1 ek None 145 Jan 4 22:40 CHANGES
-rwxr-xr-x 1 ek None 2261 Jan 4 22:40 check-version.sh
-rw-r--r-- 1 ek None 737 Jan 4 22:40 CONTRIBUTING.md
drwxr-xr-x 1 ek None 0 Jan 4 22:40 doc
-rw-r--r-- 1 ek None 107 Jan 4 22:40 FUNDING.json
drwxr-xr-x 1 ek None 0 Jan 4 22:40 fuzzing
drwxr-xr-x 1 ek None 0 Jan 4 22:40 git
-rwxr-xr-x 1 ek None 2198 Jan 4 22:40 init-tests-after-clone.sh
-rw-r--r-- 1 ek None 1503 Jan 4 22:40 LICENSE
-rw-r--r-- 1 ek None 299 Jan 4 22:40 Makefile
-rw-r--r-- 1 ek None 266 Jan 4 22:40 MANIFEST.in
-rw-r--r-- 1 ek None 2833 Jan 4 22:40 pyproject.toml
-rw-r--r-- 1 ek None 11198 Jan 4 22:40 README.md
-rw-r--r-- 1 ek None 5186 Jan 4 22:40 release-verification-key.asc
-rw-r--r-- 1 ek None 64 Jan 4 22:40 requirements.txt
-rw-r--r-- 1 ek None 178 Jan 4 22:40 requirements-dev.txt
-rw-r--r-- 1 ek None 584 Jan 4 22:40 SECURITY.md
-rwxr-xr-x 1 ek None 3926 Jan 4 22:40 setup.py
drwxr-xr-x 1 ek None 0 Jan 4 22:40 test
-rw-r--r-- 1 ek None 197 Jan 4 22:40 test-requirements.txt
-rw-r--r-- 1 ek None 1209 Jan 4 22:40 tox.ini
-rw-r--r-- 1 ek None 7 Jan 4 22:40 VERSION
ek@Glub MSYS ~/repos-msys2/GitPython
$ ls
AUTHORS doc LICENSE release-verification-key.asc test
build-release.sh FUNDING.json Makefile requirements.txt test-requirements.txt
CHANGES fuzzing MANIFEST.in requirements-dev.txt tox.ini
check-version.sh git pyproject.toml SECURITY.md VERSION
CONTRIBUTING.md init-tests-after-clone.sh README.md setup.py
ek@Glub MSYS ~/repos-msys2/GitPython
$ ./init-tests-after-clone.sh
This operation will destroy locally modified files. Continue ? [N/y]: y
hint: If you meant to check out a remote tracking branch on, e.g. 'origin',
hint: you can do so by fully qualifying the name with the --track option:
hint:
hint: git checkout --track origin/<name>
hint:
hint: If you'd like to always have checkouts of an ambiguous <name> prefer
hint: one remote, e.g. the 'origin' remote, consider setting
hint: checkout.defaultRemote=origin in your config.
fatal: 'master' matched multiple (2) remote tracking branches
Switched to a new branch 'master'
HEAD is now at cc1c6436 Merge pull request #1987 from EliahKagan/versions
HEAD is now at fb1b0512 bump patch level to prepare new version
HEAD is now at e51bf80a update GitDB submodule to latest pubslished version
HEAD is now at a7c74333 Merge pull request #1989 from EliahKagan/ci-cleanup
Submodule 'gitdb' (https://github.com/gitpython-developers/gitdb.git) registered for path 'git/ext/gitdb'
Cloning into '/home/ek/repos-msys2/GitPython/git/ext/gitdb'...
Submodule path 'git/ext/gitdb': checked out '775cfe8299ea5474f605935469359a9d1cdb49dc'
Submodule 'smmap' (https://github.com/gitpython-developers/smmap.git) registered for path 'git/ext/gitdb/gitdb/ext/smmap'
Cloning into '/home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap'...
Submodule path 'git/ext/gitdb/gitdb/ext/smmap': checked out 'f31bfa378c8840d38d31e7e11ef2b84f191a491e'
Fetching origin
Fetching upstream
Fetching kip
ek@Glub MSYS ~/repos-msys2/GitPython
$ python3.12 -m venv .venv
ek@Glub MSYS ~/repos-msys2/GitPython
$ . .venv/bin/activate
(.venv)
ek@Glub MSYS ~/repos-msys2/GitPython
$
(.venv)
ek@Glub MSYS ~/repos-msys2/GitPython
$ python -m pip install -U pip wheel
Requirement already satisfied: pip in ./.venv/lib/python3.12/site-packages (24.3.1)
Collecting wheel
Downloading wheel-0.45.1-py3-none-any.whl.metadata (2.3 kB)
Downloading wheel-0.45.1-py3-none-any.whl (72 kB)
Installing collected packages: wheel
Successfully installed wheel-0.45.1
(.venv)
ek@Glub MSYS ~/repos-msys2/GitPython
$ type pip
pip is /home/ek/repos-msys2/GitPython/.venv/bin/pip
(.venv)
ek@Glub MSYS ~/repos-msys2/GitPython
$ pip install -e '.[test]'
Obtaining file:///home/ek/repos-msys2/GitPython
Installing build dependencies ... done
Checking if build backend supports build_editable ... done
Getting requirements to build editable ... done
Preparing editable metadata (pyproject.toml) ... done
Collecting gitdb<5,>=4.0.1 (from GitPython==3.1.44)
Downloading gitdb-4.0.12-py3-none-any.whl.metadata (1.2 kB)
Collecting coverage[toml] (from GitPython==3.1.44)
Downloading coverage-7.6.10.tar.gz (803 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 803.9/803.9 kB 1.6 MB/s eta 0:00:00
Installing build dependencies ... done
Getting requirements to build wheel ... done
Preparing metadata (pyproject.toml) ... done
Collecting ddt!=1.4.3,>=1.1.1 (from GitPython==3.1.44)
Downloading ddt-1.7.2-py2.py3-none-any.whl.metadata (832 bytes)
Collecting mypy (from GitPython==3.1.44)
Downloading mypy-1.14.1-py3-none-any.whl.metadata (2.1 kB)
Collecting pre-commit (from GitPython==3.1.44)
Downloading pre_commit-4.0.1-py2.py3-none-any.whl.metadata (1.3 kB)
Collecting pytest>=7.3.1 (from GitPython==3.1.44)
Downloading pytest-8.3.4-py3-none-any.whl.metadata (7.5 kB)
Collecting pytest-cov (from GitPython==3.1.44)
Downloading pytest_cov-6.0.0-py3-none-any.whl.metadata (27 kB)
Collecting pytest-instafail (from GitPython==3.1.44)
Downloading pytest_instafail-0.5.0-py3-none-any.whl.metadata (2.2 kB)
Collecting pytest-mock (from GitPython==3.1.44)
Downloading pytest_mock-3.14.0-py3-none-any.whl.metadata (3.8 kB)
Collecting pytest-sugar (from GitPython==3.1.44)
Downloading pytest_sugar-1.0.0-py3-none-any.whl.metadata (4.4 kB)
Collecting smmap<6,>=3.0.1 (from gitdb<5,>=4.0.1->GitPython==3.1.44)
Downloading smmap-5.0.2-py3-none-any.whl.metadata (4.3 kB)
Collecting iniconfig (from pytest>=7.3.1->GitPython==3.1.44)
Downloading iniconfig-2.0.0-py3-none-any.whl.metadata (2.6 kB)
Collecting packaging (from pytest>=7.3.1->GitPython==3.1.44)
Downloading packaging-24.2-py3-none-any.whl.metadata (3.2 kB)
Collecting pluggy<2,>=1.5 (from pytest>=7.3.1->GitPython==3.1.44)
Downloading pluggy-1.5.0-py3-none-any.whl.metadata (4.8 kB)
Collecting typing_extensions>=4.6.0 (from mypy->GitPython==3.1.44)
Downloading typing_extensions-4.12.2-py3-none-any.whl.metadata (3.0 kB)
Collecting mypy_extensions>=1.0.0 (from mypy->GitPython==3.1.44)
Downloading mypy_extensions-1.0.0-py3-none-any.whl.metadata (1.1 kB)
Collecting cfgv>=2.0.0 (from pre-commit->GitPython==3.1.44)
Downloading cfgv-3.4.0-py2.py3-none-any.whl.metadata (8.5 kB)
Collecting identify>=1.0.0 (from pre-commit->GitPython==3.1.44)
Downloading identify-2.6.5-py2.py3-none-any.whl.metadata (4.4 kB)
Collecting nodeenv>=0.11.1 (from pre-commit->GitPython==3.1.44)
Downloading nodeenv-1.9.1-py2.py3-none-any.whl.metadata (21 kB)
Collecting pyyaml>=5.1 (from pre-commit->GitPython==3.1.44)
Downloading pyyaml-6.0.2.tar.gz (130 kB)
Installing build dependencies ... done
Getting requirements to build wheel ... done
Preparing metadata (pyproject.toml) ... done
Collecting virtualenv>=20.10.0 (from pre-commit->GitPython==3.1.44)
Downloading virtualenv-20.28.1-py3-none-any.whl.metadata (4.5 kB)
Collecting termcolor>=2.1.0 (from pytest-sugar->GitPython==3.1.44)
Downloading termcolor-2.5.0-py3-none-any.whl.metadata (6.1 kB)
Collecting distlib<1,>=0.3.7 (from virtualenv>=20.10.0->pre-commit->GitPython==3.1.44)
Downloading distlib-0.3.9-py2.py3-none-any.whl.metadata (5.2 kB)
Collecting filelock<4,>=3.12.2 (from virtualenv>=20.10.0->pre-commit->GitPython==3.1.44)
Downloading filelock-3.16.1-py3-none-any.whl.metadata (2.9 kB)
Collecting platformdirs<5,>=3.9.1 (from virtualenv>=20.10.0->pre-commit->GitPython==3.1.44)
Downloading platformdirs-4.3.6-py3-none-any.whl.metadata (11 kB)
Downloading ddt-1.7.2-py2.py3-none-any.whl (7.1 kB)
Downloading gitdb-4.0.12-py3-none-any.whl (62 kB)
Downloading pytest-8.3.4-py3-none-any.whl (343 kB)
Downloading mypy-1.14.1-py3-none-any.whl (2.8 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.8/2.8 MB 1.8 MB/s eta 0:00:00
Downloading pre_commit-4.0.1-py2.py3-none-any.whl (218 kB)
Downloading pytest_cov-6.0.0-py3-none-any.whl (22 kB)
Downloading pytest_instafail-0.5.0-py3-none-any.whl (4.2 kB)
Downloading pytest_mock-3.14.0-py3-none-any.whl (9.9 kB)
Downloading pytest_sugar-1.0.0-py3-none-any.whl (10 kB)
Downloading cfgv-3.4.0-py2.py3-none-any.whl (7.2 kB)
Downloading identify-2.6.5-py2.py3-none-any.whl (99 kB)
Downloading mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)
Downloading nodeenv-1.9.1-py2.py3-none-any.whl (22 kB)
Downloading packaging-24.2-py3-none-any.whl (65 kB)
Downloading pluggy-1.5.0-py3-none-any.whl (20 kB)
Downloading smmap-5.0.2-py3-none-any.whl (24 kB)
Downloading termcolor-2.5.0-py3-none-any.whl (7.8 kB)
Downloading typing_extensions-4.12.2-py3-none-any.whl (37 kB)
Downloading virtualenv-20.28.1-py3-none-any.whl (4.3 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.3/4.3 MB 1.9 MB/s eta 0:00:00
Downloading iniconfig-2.0.0-py3-none-any.whl (5.9 kB)
Downloading distlib-0.3.9-py2.py3-none-any.whl (468 kB)
Downloading filelock-3.16.1-py3-none-any.whl (16 kB)
Downloading platformdirs-4.3.6-py3-none-any.whl (18 kB)
Building wheels for collected packages: GitPython, coverage, pyyaml
Building editable for GitPython (pyproject.toml) ... done
Created wheel for GitPython: filename=GitPython-3.1.44-0.editable-py3-none-any.whl size=10107 sha256=7e5041a50e2c9584215574726c8634b39c4e05576de74a119eed5cf2fec808a0
Stored in directory: /tmp/pip-ephem-wheel-cache-jpv4pj20/wheels/a5/22/6f/2ffdb82ea5117c8ffb166ce4a15acfcd53b0918727e4d73245
Building wheel for coverage (pyproject.toml) ... done
Created wheel for coverage: filename=coverage-7.6.10-py3-none-any.whl size=200217 sha256=d13864dd6c1db048ac08ade5bc60001ec57192362afdbb1324048609a34dd5f9
Stored in directory: /home/ek/.cache/pip/wheels/ca/7d/56/20d127c22de63e3110a99137202ef2e87034f3cd59dbac57f8
Building wheel for pyyaml (pyproject.toml) ... done
Created wheel for pyyaml: filename=PyYAML-6.0.2-cp312-cp312-msys_3_5_4_x86_64.whl size=45366 sha256=6fe92edfe047e286a9239121eb3cc96b8adde63fc3ee2f62d06fb054778b5d7e
Stored in directory: /home/ek/.cache/pip/wheels/db/db/e2/4a1264f6c5192c518338cd8c226caae9f43c610fdb76c1ebcb
Successfully built GitPython coverage pyyaml
Installing collected packages: distlib, ddt, typing_extensions, termcolor, smmap, pyyaml, pluggy, platformdirs, packaging, nodeenv, mypy_extensions, iniconfig, identify, filelock, coverage, cfgv, virtualenv, pytest, mypy, gitdb, pytest-sugar, pytest-mock, pytest-instafail, pytest-cov, pre-commit, GitPython
Successfully installed GitPython-3.1.44 cfgv-3.4.0 coverage-7.6.10 ddt-1.7.2 distlib-0.3.9 filelock-3.16.1 gitdb-4.0.12 identify-2.6.5 iniconfig-2.0.0 mypy-1.14.1 mypy_extensions-1.0.0 nodeenv-1.9.1 packaging-24.2 platformdirs-4.3.6 pluggy-1.5.0 pre-commit-4.0.1 pytest-8.3.4 pytest-cov-6.0.0 pytest-instafail-0.5.0 pytest-mock-3.14.0 pytest-sugar-1.0.0 pyyaml-6.0.2 smmap-5.0.2 termcolor-2.5.0 typing_extensions-4.12.2 virtualenv-20.28.1
(.venv)
ek@Glub MSYS ~/repos-msys2/GitPython
$ pytest
Test session starts (platform: cygwin, Python 3.12.8, pytest 8.3.4, pytest-sugar 1.0.0)
rootdir: /home/ek/repos-msys2/GitPython
configfile: pyproject.toml
testpaths: test
plugins: cov-6.0.0, instafail-0.5.0, mock-3.14.0, sugar-1.0.0
collected 668 items
test/deprecation/test_basic.py ✓✓✓✓✓✓✓✓✓✓✓✓ 2% ▎
test/deprecation/test_cmd_git.py ✓✓✓✓✓✓✓✓✓✓✓✓✓ 4% ▍
test/deprecation/test_compat.py ✓✓✓ 4% ▌
test/deprecation/test_toplevel.py ✓✓✓✓✓✓✓✓✓✓ 6% ▋
test/deprecation/test_types.py ✓✓✓ 6% ▋
test/performance/test_commit.py ✓✓✓✓ 7% ▋
test/performance/test_odb.py ✓ 7% ▊
test/performance/test_streams.py ✓ 7% ▊
test/test_actor.py ✓✓✓✓ 8% ▊
test/test_base.py ✓✓✓✓✓✓✓ 9% ▉
test/test_blob.py ✓✓✓ 9% ▉
test/test_blob_filter.py ✓✓✓✓ 10% █
test/test_clone.py ✓ 10% █
test/test_commit.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 14% █▍
test/test_config.py ✓✓✓✓✓✓✓✓✓✓s✓✓✓✓✓✓✓✓✓ 17% █▋
test/test_db.py ✓ 17% █▊
test/test_diff.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 20% █▉
――――――――――――――――――――――――――――――――――――――――― TestDiff.test_diff_with_staged_file ――――――――――――――――――――――――――――――――――――――――――
self = <test.test_diff.TestDiff testMethod=test_diff_with_staged_file>
rw_dir = '/tmp/test_diff_with_staged_file_7xwzssk'
@with_rw_directory
def test_diff_with_staged_file(self, rw_dir):
# SET UP INDEX WITH MULTIPLE STAGES
r = Repo.init(rw_dir)
fp = osp.join(rw_dir, "hello.txt")
with open(fp, "w") as fs:
fs.write("hello world")
r.git.add(Git.polish_url(fp))
> r.git.commit(message="init")
test/test_diff.py:59:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
git/cmd.py:986: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git/cmd.py:1599: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x6ffffe4244c0>, command = ['git', 'commit', '--message=init'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'!C:': 'C:\\msys64\\home\\ek', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'AZURE_CLI_PATH': 'C:\\Users\\ek\\scoop\\apps\\azure-cli\\current\\bin', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
cwd = '/tmp/test_diff_with_staged_file_7xwzssk', inline_env = None
cmd_not_found_exception = <class 'FileNotFoundError'>, stdout_sink = -1
communicate = <bound method Popen.communicate of <Popen: returncode: 128 args: ['git', 'commit', '--message=init']>>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
R"""Handle executing the command, and consume and return the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to :class:`subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render `with_extended_output`
and `with_exceptions` ineffective - the caller will have to deal with
the details. It is important to note that the process will be placed
into an :class:`AutoInterrupt` wrapper that will interrupt the process
once it goes out of scope. If you use the command in iterators, you
should pass the whole process instance instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
copied to the given stream instead of being returned as a string.
This feature only has any effect if `as_process` is ``False``.
:param stdout_as_string:
If ``False``, the command's standard output will be bytes. Otherwise, it
will be decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if `as_process` is set to
``True``. It is set to ``None`` by default and will let the process run
until the timeout is explicitly specified. Uses of this feature should be
carefully considered, due to the following limitations:
1. This feature is not supported at all on Windows.
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
enumerate child processes, which is available on most GNU/Linux systems
but not most others.
3. Deeper descendants do not receive signals, though they may sometimes
terminate as a consequence of their parent processes being killed.
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
effects on a repository. For example, stale locks in case of
:manpage:`git-gc(1)` could render the repository incapable of accepting
changes until the lock is manually removed.
:param with_stdout:
If ``True``, default ``True``, we open stdout on the created process.
:param universal_newlines:
If ``True``, pipes will be opened as text, and lines are split at all known
line endings.
:param shell:
Whether to invoke commands through a shell
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
If this is not ``None``, it overrides :attr:`USE_SHELL`.
Passing ``shell=True`` to this or any other GitPython function should be
avoided, as it is unsafe under most circumstances. This is because it is
typically not feasible to fully consider and account for the effect of shell
expansions, especially when passing ``shell=True`` to other methods that
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
needed (nor useful) to work around any known operating system specific
issues.
:param env:
A dictionary of environment variables to be passed to
:class:`subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the `output_stream`
in one invocation of its ``write()`` method. If the given number is not
positive then the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
that some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output), if `extended_output` is ``False`` (Default)
* tuple(int(status), str(stdout), str(stderr)),
if `extended_output` is ``True``
If `output_stream` is ``True``, the stdout value will be your output stream:
* output_stream, if `extended_output` is ``False``
* tuple(int(status), output_stream, str(stderr)),
if `extended_output` is ``True``
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise git.exc.GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method, you
must update the ``execute_kwargs`` variable housed in this module.
"""
# Remove password for the command if present.
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process.
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ASCII English, which is what some parsing
# code may expect.
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell = super().__getattribute__("USE_SHELL")
_logger.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
proc = safer_popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=(istream or DEVNULL),
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
universal_newlines=universal_newlines,
encoding=defenc if universal_newlines else None,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# Replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
if sys.platform != "win32" and kill_after_timeout is not None:
# Help mypy figure out this is not None even when used inside communicate().
timeout = kill_after_timeout
def kill_process(pid: int) -> None:
"""Callback to kill a process.
This callback implementation would be ineffective and unsafe on Windows.
"""
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
os.kill(pid, signal.SIGKILL)
for child_pid in child_pids:
try:
os.kill(child_pid, signal.SIGKILL)
except OSError:
pass
# Tell the main routine that the process was killed.
kill_check.set()
except OSError:
# It is possible that the process gets completed in the duration
# after timeout happens and before we try to kill the process.
pass
return
def communicate() -> Tuple[AnyStr, AnyStr]:
watchdog.start()
out, err = proc.communicate()
watchdog.cancel()
if kill_check.is_set():
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
timeout,
)
if not universal_newlines:
err = err.encode(defenc)
return out, err
# END helpers
kill_check = threading.Event()
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
else:
communicate = proc.communicate
# Wait for the process to return.
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
stdout_value, stderr_value = communicate()
# Strip trailing "\n".
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# END as_text
if stderr_value:
_logger.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
_logger.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git commit --message=init
E stderr: 'Author identity unknown
E
E *** Please tell me who you are.
E
E Run
E
E git config --global user.email "[email protected]"
E git config --global user.name "Your Name"
E
E to set your account's default identity.
E Omit --global to set the identity only in this repository.
E
E fatal: unable to auto-detect email address (got 'ek@Glub.(none)')'
git/cmd.py:1389: GitCommandError
test/test_diff.py ⨯✓✓ 20% ██
―――――――――――――――――――――――――――――――――――――――――――― TestDiff.test_rename_override ―――――――――――――――――――――――――――――――――――――――――――――
self = <test.test_diff.TestDiff testMethod=test_rename_override>, rw_dir = '/tmp/test_rename_overrideo3x78kmd'
@with_rw_directory
def test_rename_override(self, rw_dir):
"""Test disabling of diff rename detection."""
# Create and commit file_a.txt.
repo = Repo.init(rw_dir)
file_a = osp.join(rw_dir, "file_a.txt")
with open(file_a, "w", encoding="utf-8") as outfile:
outfile.write("hello world\n")
repo.git.add(Git.polish_url(file_a))
> repo.git.commit(message="Added file_a.txt")
test/test_diff.py:432:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
git/cmd.py:986: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git/cmd.py:1599: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x6ffffe426260>, command = ['git', 'commit', '--message=Added file_a.txt'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'!C:': 'C:\\msys64\\home\\ek', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'AZURE_CLI_PATH': 'C:\\Users\\ek\\scoop\\apps\\azure-cli\\current\\bin', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}, cwd = '/tmp/test_rename_overrideo3x78kmd'
inline_env = None, cmd_not_found_exception = <class 'FileNotFoundError'>, stdout_sink = -1
communicate = <bound method Popen.communicate of <Popen: returncode: 128 args: ['git', 'commit', '--message=Added file_a.txt']>>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
R"""Handle executing the command, and consume and return the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to :class:`subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render `with_extended_output`
and `with_exceptions` ineffective - the caller will have to deal with
the details. It is important to note that the process will be placed
into an :class:`AutoInterrupt` wrapper that will interrupt the process
once it goes out of scope. If you use the command in iterators, you
should pass the whole process instance instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
copied to the given stream instead of being returned as a string.
This feature only has any effect if `as_process` is ``False``.
:param stdout_as_string:
If ``False``, the command's standard output will be bytes. Otherwise, it
will be decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if `as_process` is set to
``True``. It is set to ``None`` by default and will let the process run
until the timeout is explicitly specified. Uses of this feature should be
carefully considered, due to the following limitations:
1. This feature is not supported at all on Windows.
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
enumerate child processes, which is available on most GNU/Linux systems
but not most others.
3. Deeper descendants do not receive signals, though they may sometimes
terminate as a consequence of their parent processes being killed.
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
effects on a repository. For example, stale locks in case of
:manpage:`git-gc(1)` could render the repository incapable of accepting
changes until the lock is manually removed.
:param with_stdout:
If ``True``, default ``True``, we open stdout on the created process.
:param universal_newlines:
If ``True``, pipes will be opened as text, and lines are split at all known
line endings.
:param shell:
Whether to invoke commands through a shell
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
If this is not ``None``, it overrides :attr:`USE_SHELL`.
Passing ``shell=True`` to this or any other GitPython function should be
avoided, as it is unsafe under most circumstances. This is because it is
typically not feasible to fully consider and account for the effect of shell
expansions, especially when passing ``shell=True`` to other methods that
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
needed (nor useful) to work around any known operating system specific
issues.
:param env:
A dictionary of environment variables to be passed to
:class:`subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the `output_stream`
in one invocation of its ``write()`` method. If the given number is not
positive then the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
that some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output), if `extended_output` is ``False`` (Default)
* tuple(int(status), str(stdout), str(stderr)),
if `extended_output` is ``True``
If `output_stream` is ``True``, the stdout value will be your output stream:
* output_stream, if `extended_output` is ``False``
* tuple(int(status), output_stream, str(stderr)),
if `extended_output` is ``True``
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise git.exc.GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method, you
must update the ``execute_kwargs`` variable housed in this module.
"""
# Remove password for the command if present.
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process.
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ASCII English, which is what some parsing
# code may expect.
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell = super().__getattribute__("USE_SHELL")
_logger.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
proc = safer_popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=(istream or DEVNULL),
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
universal_newlines=universal_newlines,
encoding=defenc if universal_newlines else None,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# Replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
if sys.platform != "win32" and kill_after_timeout is not None:
# Help mypy figure out this is not None even when used inside communicate().
timeout = kill_after_timeout
def kill_process(pid: int) -> None:
"""Callback to kill a process.
This callback implementation would be ineffective and unsafe on Windows.
"""
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
os.kill(pid, signal.SIGKILL)
for child_pid in child_pids:
try:
os.kill(child_pid, signal.SIGKILL)
except OSError:
pass
# Tell the main routine that the process was killed.
kill_check.set()
except OSError:
# It is possible that the process gets completed in the duration
# after timeout happens and before we try to kill the process.
pass
return
def communicate() -> Tuple[AnyStr, AnyStr]:
watchdog.start()
out, err = proc.communicate()
watchdog.cancel()
if kill_check.is_set():
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
timeout,
)
if not universal_newlines:
err = err.encode(defenc)
return out, err
# END helpers
kill_check = threading.Event()
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
else:
communicate = proc.communicate
# Wait for the process to return.
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
stdout_value, stderr_value = communicate()
# Strip trailing "\n".
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# END as_text
if stderr_value:
_logger.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
_logger.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git commit --message=Added file_a.txt
E stderr: 'Author identity unknown
E
E *** Please tell me who you are.
E
E Run
E
E git config --global user.email "[email protected]"
E git config --global user.name "Your Name"
E
E to set your account's default identity.
E Omit --global to set the identity only in this repository.
E
E fatal: unable to auto-detect email address (got 'ek@Glub.(none)')'
git/cmd.py:1389: GitCommandError
test/test_diff.py ⨯ 20% ██
test/test_docs.py ✓ 20% ██▏
――――――――――――――――――――――――――――――――――――――――――― Tutorials.test_init_repo_object ――――――――――――――――――――――――――――――――――――――――――――
self = <test.test_docs.Tutorials testMethod=test_init_repo_object>, rw_dir = '/tmp/test_init_repo_object4oa68wom'
@with_rw_directory
def test_init_repo_object(self, rw_dir):
# [1-test_init_repo_object]
from git import Repo
# rorepo is a Repo instance pointing to the git-python repository.
# For all you know, the first argument to Repo is a path to the repository you
# want to work with.
repo = Repo(self.rorepo.working_tree_dir)
assert not repo.bare
# ![1-test_init_repo_object]
# [2-test_init_repo_object]
bare_repo = Repo.init(os.path.join(rw_dir, "bare-repo"), bare=True)
assert bare_repo.bare
# ![2-test_init_repo_object]
# [3-test_init_repo_object]
repo.config_reader() # Get a config reader for read-only access.
with repo.config_writer(): # Get a config writer to change configuration.
pass # Call release() to be sure changes are written and locks are released.
# ![3-test_init_repo_object]
# [4-test_init_repo_object]
assert not bare_repo.is_dirty() # Check the dirty state.
repo.untracked_files # Retrieve a list of untracked files.
# ['my_untracked_file']
# ![4-test_init_repo_object]
# [5-test_init_repo_object]
cloned_repo = repo.clone(os.path.join(rw_dir, "to/this/path"))
assert cloned_repo.__class__ is Repo # Clone an existing repository.
assert Repo.init(os.path.join(rw_dir, "path/for/new/repo")).__class__ is Repo
# ![5-test_init_repo_object]
# [6-test_init_repo_object]
with open(os.path.join(rw_dir, "repo.tar"), "wb") as fp:
repo.archive(fp)
# ![6-test_init_repo_object]
# repository paths
# [7-test_init_repo_object]
assert os.path.isdir(cloned_repo.working_tree_dir) # Directory with your work files.
assert cloned_repo.git_dir.startswith(cloned_repo.working_tree_dir) # Directory containing the git repository.
assert bare_repo.working_tree_dir is None # Bare repositories have no working tree.
# ![7-test_init_repo_object]
# heads, tags and references
# heads are branches in git-speak
# [8-test_init_repo_object]
self.assertEqual(
repo.head.ref,
repo.heads.master, # head is a sym-ref pointing to master.
"It's ok if TC not running from `master`.",
)
self.assertEqual(repo.tags["0.3.5"], repo.tag("refs/tags/0.3.5")) # You can access tags in various ways too.
self.assertEqual(repo.refs.master, repo.heads["master"]) # .refs provides all refs, i.e. heads...
if "TRAVIS" not in os.environ:
self.assertEqual(repo.refs["origin/master"], repo.remotes.origin.refs.master) # ... remotes ...
self.assertEqual(repo.refs["0.3.5"], repo.tags["0.3.5"]) # ... and tags.
# ![8-test_init_repo_object]
# Create a new head/branch.
# [9-test_init_repo_object]
new_branch = cloned_repo.create_head("feature") # Create a new branch ...
assert cloned_repo.active_branch != new_branch # which wasn't checked out yet ...
self.assertEqual(new_branch.commit, cloned_repo.active_branch.commit) # pointing to the checked-out commit.
# It's easy to let a branch point to the previous commit, without affecting anything else.
# Each reference provides access to the git object it points to, usually commits.
assert new_branch.set_commit("HEAD~1").commit == cloned_repo.active_branch.commit.parents[0]
# ![9-test_init_repo_object]
# Create a new tag reference.
# [10-test_init_repo_object]
> past = cloned_repo.create_tag(
"past",
ref=new_branch,
message="This is a tag-object pointing to %s" % new_branch.name,
)
test/test_docs.py:103:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
git/repo/base.py:612: in create_tag
return TagReference.create(self, path, ref, message, force, **kwargs)
git/refs/tag.py:145: in create
repo.git.tag(*args, **kwargs)
git/cmd.py:986: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git/cmd.py:1599: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x6ffffe195780>
command = ['git', 'tag', '-m', 'This is a tag-object pointing to feature', 'past', 'feature'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'!C:': 'C:\\msys64\\home\\ek', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'AZURE_CLI_PATH': 'C:\\Users\\ek\\scoop\\apps\\azure-cli\\current\\bin', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
cwd = '/tmp/test_init_repo_object4oa68wom/to/this/path', inline_env = None
cmd_not_found_exception = <class 'FileNotFoundError'>, stdout_sink = -1
communicate = <bound method Popen.communicate of <Popen: returncode: 128 args: ['git', 'tag', '-m', 'This is a tag-object poi...>>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
R"""Handle executing the command, and consume and return the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to :class:`subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render `with_extended_output`
and `with_exceptions` ineffective - the caller will have to deal with
the details. It is important to note that the process will be placed
into an :class:`AutoInterrupt` wrapper that will interrupt the process
once it goes out of scope. If you use the command in iterators, you
should pass the whole process instance instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
copied to the given stream instead of being returned as a string.
This feature only has any effect if `as_process` is ``False``.
:param stdout_as_string:
If ``False``, the command's standard output will be bytes. Otherwise, it
will be decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if `as_process` is set to
``True``. It is set to ``None`` by default and will let the process run
until the timeout is explicitly specified. Uses of this feature should be
carefully considered, due to the following limitations:
1. This feature is not supported at all on Windows.
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
enumerate child processes, which is available on most GNU/Linux systems
but not most others.
3. Deeper descendants do not receive signals, though they may sometimes
terminate as a consequence of their parent processes being killed.
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
effects on a repository. For example, stale locks in case of
:manpage:`git-gc(1)` could render the repository incapable of accepting
changes until the lock is manually removed.
:param with_stdout:
If ``True``, default ``True``, we open stdout on the created process.
:param universal_newlines:
If ``True``, pipes will be opened as text, and lines are split at all known
line endings.
:param shell:
Whether to invoke commands through a shell
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
If this is not ``None``, it overrides :attr:`USE_SHELL`.
Passing ``shell=True`` to this or any other GitPython function should be
avoided, as it is unsafe under most circumstances. This is because it is
typically not feasible to fully consider and account for the effect of shell
expansions, especially when passing ``shell=True`` to other methods that
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
needed (nor useful) to work around any known operating system specific
issues.
:param env:
A dictionary of environment variables to be passed to
:class:`subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the `output_stream`
in one invocation of its ``write()`` method. If the given number is not
positive then the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
that some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output), if `extended_output` is ``False`` (Default)
* tuple(int(status), str(stdout), str(stderr)),
if `extended_output` is ``True``
If `output_stream` is ``True``, the stdout value will be your output stream:
* output_stream, if `extended_output` is ``False``
* tuple(int(status), output_stream, str(stderr)),
if `extended_output` is ``True``
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise git.exc.GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method, you
must update the ``execute_kwargs`` variable housed in this module.
"""
# Remove password for the command if present.
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process.
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ASCII English, which is what some parsing
# code may expect.
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell = super().__getattribute__("USE_SHELL")
_logger.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
proc = safer_popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=(istream or DEVNULL),
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
universal_newlines=universal_newlines,
encoding=defenc if universal_newlines else None,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# Replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
if sys.platform != "win32" and kill_after_timeout is not None:
# Help mypy figure out this is not None even when used inside communicate().
timeout = kill_after_timeout
def kill_process(pid: int) -> None:
"""Callback to kill a process.
This callback implementation would be ineffective and unsafe on Windows.
"""
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
os.kill(pid, signal.SIGKILL)
for child_pid in child_pids:
try:
os.kill(child_pid, signal.SIGKILL)
except OSError:
pass
# Tell the main routine that the process was killed.
kill_check.set()
except OSError:
# It is possible that the process gets completed in the duration
# after timeout happens and before we try to kill the process.
pass
return
def communicate() -> Tuple[AnyStr, AnyStr]:
watchdog.start()
out, err = proc.communicate()
watchdog.cancel()
if kill_check.is_set():
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
timeout,
)
if not universal_newlines:
err = err.encode(defenc)
return out, err
# END helpers
kill_check = threading.Event()
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
else:
communicate = proc.communicate
# Wait for the process to return.
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
stdout_value, stderr_value = communicate()
# Strip trailing "\n".
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# END as_text
if stderr_value:
_logger.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
_logger.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git tag -m This is a tag-object pointing to feature past feature
E stderr: 'Committer identity unknown
E
E *** Please tell me who you are.
E
E Run
E
E git config --global user.email "[email protected]"
E git config --global user.name "Your Name"
E
E to set your account's default identity.
E Omit --global to set the identity only in this repository.
E
E fatal: unable to auto-detect email address (got 'ek@Glub.(none)')'
git/cmd.py:1389: GitCommandError
test/test_docs.py ⨯ 21% ██▏
―――――――――――――――――――――――――――――――――――――――― Tutorials.test_references_and_objects ―――――――――――――――――――――――――――――――――――――――――
self = <test.test_docs.Tutorials testMethod=test_references_and_objects>
rw_dir = '/tmp/test_references_and_objectsm7_6lr5v'
@with_rw_directory
def test_references_and_objects(self, rw_dir):
# [1-test_references_and_objects]
import git
repo = git.Repo.clone_from(self._small_repo_url(), os.path.join(rw_dir, "repo"), branch="master")
heads = repo.heads
master = heads.master # Lists can be accessed by name for convenience.
master.commit # the commit pointed to by head called master.
master.rename("new_name") # Rename heads.
master.rename("master")
# ![1-test_references_and_objects]
# [2-test_references_and_objects]
tags = repo.tags
tagref = tags[0]
tagref.tag # Tags may have tag objects carrying additional information
tagref.commit # but they always point to commits.
repo.delete_tag(tagref) # Delete or
repo.create_tag("my_tag") # create tags using the repo for convenience.
# ![2-test_references_and_objects]
# [3-test_references_and_objects]
head = repo.head # The head points to the active branch/ref.
master = head.reference # Retrieve the reference the head points to.
master.commit # From here you use it as any other reference.
# ![3-test_references_and_objects]
#
# [4-test_references_and_objects]
log = master.log()
log[0] # first (i.e. oldest) reflog entry
log[-1] # last (i.e. most recent) reflog entry
# ![4-test_references_and_objects]
# [5-test_references_and_objects]
new_branch = repo.create_head("new") # Create a new one.
new_branch.commit = "HEAD~10" # Set branch to another commit without changing index or working trees.
repo.delete_head(new_branch) # Delete an existing head - only works if it is not checked out.
# ![5-test_references_and_objects]
# [6-test_references_and_objects]
> new_tag = repo.create_tag("my_new_tag", message="my message")
test/test_docs.py:251:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
git/repo/base.py:612: in create_tag
return TagReference.create(self, path, ref, message, force, **kwargs)
git/refs/tag.py:145: in create
repo.git.tag(*args, **kwargs)
git/cmd.py:986: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git/cmd.py:1599: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x6ffffe17b040>, command = ['git', 'tag', '-m', 'my message', 'my_new_tag', 'HEAD']
istream = None, with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None
stdout_as_string = True, kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'!C:': 'C:\\msys64\\home\\ek', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'AZURE_CLI_PATH': 'C:\\Users\\ek\\scoop\\apps\\azure-cli\\current\\bin', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
cwd = '/tmp/test_references_and_objectsm7_6lr5v/repo', inline_env = None
cmd_not_found_exception = <class 'FileNotFoundError'>, stdout_sink = -1
communicate = <bound method Popen.communicate of <Popen: returncode: 128 args: ['git', 'tag', '-m', 'my message', 'my_new_tag...>>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
R"""Handle executing the command, and consume and return the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to :class:`subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render `with_extended_output`
and `with_exceptions` ineffective - the caller will have to deal with
the details. It is important to note that the process will be placed
into an :class:`AutoInterrupt` wrapper that will interrupt the process
once it goes out of scope. If you use the command in iterators, you
should pass the whole process instance instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
copied to the given stream instead of being returned as a string.
This feature only has any effect if `as_process` is ``False``.
:param stdout_as_string:
If ``False``, the command's standard output will be bytes. Otherwise, it
will be decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if `as_process` is set to
``True``. It is set to ``None`` by default and will let the process run
until the timeout is explicitly specified. Uses of this feature should be
carefully considered, due to the following limitations:
1. This feature is not supported at all on Windows.
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
enumerate child processes, which is available on most GNU/Linux systems
but not most others.
3. Deeper descendants do not receive signals, though they may sometimes
terminate as a consequence of their parent processes being killed.
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
effects on a repository. For example, stale locks in case of
:manpage:`git-gc(1)` could render the repository incapable of accepting
changes until the lock is manually removed.
:param with_stdout:
If ``True``, default ``True``, we open stdout on the created process.
:param universal_newlines:
If ``True``, pipes will be opened as text, and lines are split at all known
line endings.
:param shell:
Whether to invoke commands through a shell
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
If this is not ``None``, it overrides :attr:`USE_SHELL`.
Passing ``shell=True`` to this or any other GitPython function should be
avoided, as it is unsafe under most circumstances. This is because it is
typically not feasible to fully consider and account for the effect of shell
expansions, especially when passing ``shell=True`` to other methods that
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
needed (nor useful) to work around any known operating system specific
issues.
:param env:
A dictionary of environment variables to be passed to
:class:`subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the `output_stream`
in one invocation of its ``write()`` method. If the given number is not
positive then the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
that some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output), if `extended_output` is ``False`` (Default)
* tuple(int(status), str(stdout), str(stderr)),
if `extended_output` is ``True``
If `output_stream` is ``True``, the stdout value will be your output stream:
* output_stream, if `extended_output` is ``False``
* tuple(int(status), output_stream, str(stderr)),
if `extended_output` is ``True``
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise git.exc.GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method, you
must update the ``execute_kwargs`` variable housed in this module.
"""
# Remove password for the command if present.
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process.
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ASCII English, which is what some parsing
# code may expect.
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell = super().__getattribute__("USE_SHELL")
_logger.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
proc = safer_popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=(istream or DEVNULL),
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
universal_newlines=universal_newlines,
encoding=defenc if universal_newlines else None,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# Replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
if sys.platform != "win32" and kill_after_timeout is not None:
# Help mypy figure out this is not None even when used inside communicate().
timeout = kill_after_timeout
def kill_process(pid: int) -> None:
"""Callback to kill a process.
This callback implementation would be ineffective and unsafe on Windows.
"""
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
os.kill(pid, signal.SIGKILL)
for child_pid in child_pids:
try:
os.kill(child_pid, signal.SIGKILL)
except OSError:
pass
# Tell the main routine that the process was killed.
kill_check.set()
except OSError:
# It is possible that the process gets completed in the duration
# after timeout happens and before we try to kill the process.
pass
return
def communicate() -> Tuple[AnyStr, AnyStr]:
watchdog.start()
out, err = proc.communicate()
watchdog.cancel()
if kill_check.is_set():
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
timeout,
)
if not universal_newlines:
err = err.encode(defenc)
return out, err
# END helpers
kill_check = threading.Event()
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
else:
communicate = proc.communicate
# Wait for the process to return.
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
stdout_value, stderr_value = communicate()
# Strip trailing "\n".
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# END as_text
if stderr_value:
_logger.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
_logger.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git tag -m my message my_new_tag HEAD
E stderr: 'Committer identity unknown
E
E *** Please tell me who you are.
E
E Run
E
E git config --global user.email "[email protected]"
E git config --global user.name "Your Name"
E
E to set your account's default identity.
E Omit --global to set the identity only in this repository.
E
E fatal: unable to auto-detect email address (got 'ek@Glub.(none)')'
git/cmd.py:1389: GitCommandError
test/test_docs.py ⨯X 21% ██▏
test/test_exc.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 34% ███▍
✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 44% ████▌
test/test_fun.py ✓✓✓✓✓✓✓ 45% ████▋
test/test_git.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓s✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 58% █████▉
✓✓✓ 59% █████▉
test/test_imports.py ✓✓✓ 59% █████▉
test/test_index.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 62% ██████▎
―――――――――――――――――――――――――――――――――――――――――――― TestIndex.test_index_mutation ―――――――――――――――――――――――――――――――――――――――――――――
self = <test.test_index.TestIndex testMethod=test_index_mutation>
rw_repo = <git.repo.base.Repo '/tmp/non_bare_test_index_mutationn7njifhq/.git'>
@pytest.mark.xfail(
sys.platform == "win32" and Git().config("core.symlinks") == "true",
reason="Assumes symlinks are not created on Windows and opens a symlink to a nonexistent target.",
raises=FileNotFoundError,
)
@with_rw_repo("0.1.6")
def test_index_mutation(self, rw_repo):
index = rw_repo.index
num_entries = len(index.entries)
cur_head = rw_repo.head
uname = "Thomas Müller"
umail = "[email protected]"
with rw_repo.config_writer() as writer:
writer.set_value("user", "name", uname)
writer.set_value("user", "email", umail)
self.assertEqual(writer.get_value("user", "name"), uname)
# Remove all of the files, provide a wild mix of paths, BaseIndexEntries,
# IndexEntries.
def mixed_iterator():
count = 0
for entry in index.entries.values():
type_id = count % 5
if type_id == 0: # path (str)
yield entry.path
elif type_id == 1: # path (PathLike)
yield Path(entry.path)
elif type_id == 2: # blob
yield Blob(rw_repo, entry.binsha, entry.mode, entry.path)
elif type_id == 3: # BaseIndexEntry
yield BaseIndexEntry(entry[:4])
elif type_id == 4: # IndexEntry
yield entry
else:
raise AssertionError("Invalid Type")
count += 1
# END for each entry
# END mixed iterator
deleted_files = index.remove(mixed_iterator(), working_tree=False)
assert deleted_files
self.assertEqual(self._count_existing(rw_repo, deleted_files), len(deleted_files))
self.assertEqual(len(index.entries), 0)
# Reset the index to undo our changes.
index.reset()
self.assertEqual(len(index.entries), num_entries)
# Remove with working copy.
deleted_files = index.remove(mixed_iterator(), working_tree=True)
assert deleted_files
self.assertEqual(self._count_existing(rw_repo, deleted_files), 0)
# Reset everything.
index.reset(working_tree=True)
self.assertEqual(self._count_existing(rw_repo, deleted_files), len(deleted_files))
# Invalid type.
self.assertRaises(TypeError, index.remove, [1])
# Absolute path.
deleted_files = index.remove([osp.join(rw_repo.working_tree_dir, "lib")], r=True)
assert len(deleted_files) > 1
self.assertRaises(ValueError, index.remove, ["/doesnt/exists"])
# TEST COMMITTING
# Commit changed index.
cur_commit = cur_head.commit
commit_message = "commit default head by Frèderic Çaufl€"
new_commit = index.commit(commit_message, head=False)
assert cur_commit != new_commit
self.assertEqual(new_commit.author.name, uname)
self.assertEqual(new_commit.author.email, umail)
self.assertEqual(new_commit.committer.name, uname)
self.assertEqual(new_commit.committer.email, umail)
self.assertEqual(new_commit.message, commit_message)
self.assertEqual(new_commit.parents[0], cur_commit)
self.assertEqual(len(new_commit.parents), 1)
self.assertEqual(cur_head.commit, cur_commit)
# Commit with other actor.
cur_commit = cur_head.commit
my_author = Actor("Frèderic Çaufl€", "[email protected]")
my_committer = Actor("Committing Frèderic Çaufl€", "[email protected]")
commit_actor = index.commit(commit_message, author=my_author, committer=my_committer)
assert cur_commit != commit_actor
self.assertEqual(commit_actor.author.name, "Frèderic Çaufl€")
self.assertEqual(commit_actor.author.email, "[email protected]")
self.assertEqual(commit_actor.committer.name, "Committing Frèderic Çaufl€")
self.assertEqual(commit_actor.committer.email, "[email protected]")
self.assertEqual(commit_actor.message, commit_message)
self.assertEqual(commit_actor.parents[0], cur_commit)
self.assertEqual(len(new_commit.parents), 1)
self.assertEqual(cur_head.commit, commit_actor)
self.assertEqual(cur_head.log()[-1].actor, my_committer)
# Commit with author_date and commit_date.
cur_commit = cur_head.commit
commit_message = "commit with dates by Avinash Sajjanshetty"
new_commit = index.commit(
commit_message,
author_date="2006-04-07T22:13:13",
commit_date="2005-04-07T22:13:13",
)
assert cur_commit != new_commit
print(new_commit.authored_date, new_commit.committed_date)
self.assertEqual(new_commit.message, commit_message)
self.assertEqual(new_commit.authored_date, 1144447993)
self.assertEqual(new_commit.committed_date, 1112911993)
# Same index, no parents.
commit_message = "index without parents"
commit_no_parents = index.commit(commit_message, parent_commits=[], head=True)
self.assertEqual(commit_no_parents.message, commit_message)
self.assertEqual(len(commit_no_parents.parents), 0)
self.assertEqual(cur_head.commit, commit_no_parents)
# same index, multiple parents.
commit_message = "Index with multiple parents\n commit with another line"
commit_multi_parent = index.commit(commit_message, parent_commits=(commit_no_parents, new_commit))
self.assertEqual(commit_multi_parent.message, commit_message)
self.assertEqual(len(commit_multi_parent.parents), 2)
self.assertEqual(commit_multi_parent.parents[0], commit_no_parents)
self.assertEqual(commit_multi_parent.parents[1], new_commit)
self.assertEqual(cur_head.commit, commit_multi_parent)
# Re-add all files in lib.
# Get the lib folder back on disk, but get an index without it.
index.reset(new_commit.parents[0], working_tree=True).reset(new_commit, working_tree=False)
lib_file_path = osp.join("lib", "git", "__init__.py")
assert (lib_file_path, 0) not in index.entries
assert osp.isfile(osp.join(rw_repo.working_tree_dir, lib_file_path))
# Directory.
entries = index.add(["lib"], fprogress=self._fprogress_add)
self._assert_entries(entries)
self._assert_fprogress(entries)
assert len(entries) > 1
# Glob.
entries = index.reset(new_commit).add([osp.join("lib", "git", "*.py")], fprogress=self._fprogress_add)
self._assert_entries(entries)
self._assert_fprogress(entries)
self.assertEqual(len(entries), 14)
# Same file.
entries = index.reset(new_commit).add(
[osp.join(rw_repo.working_tree_dir, "lib", "git", "head.py")] * 2,
fprogress=self._fprogress_add,
)
self._assert_entries(entries)
self.assertEqual(entries[0].mode & 0o644, 0o644)
# Would fail, test is too primitive to handle this case.
# self._assert_fprogress(entries)
self._reset_progress()
self.assertEqual(len(entries), 2)
# Missing path.
self.assertRaises(OSError, index.reset(new_commit).add, ["doesnt/exist/must/raise"])
# Blob from older revision overrides current index revision.
old_blob = new_commit.parents[0].tree.blobs[0]
entries = index.reset(new_commit).add([old_blob], fprogress=self._fprogress_add)
self._assert_entries(entries)
self._assert_fprogress(entries)
self.assertEqual(index.entries[(old_blob.path, 0)].hexsha, old_blob.hexsha)
self.assertEqual(len(entries), 1)
# Mode 0 not allowed.
null_hex_sha = Diff.NULL_HEX_SHA
null_bin_sha = b"\0" * 20
self.assertRaises(
ValueError,
index.reset(new_commit).add,
[BaseIndexEntry((0, null_bin_sha, 0, "doesntmatter"))],
)
# Add new file.
new_file_relapath = "my_new_file"
self._make_file(new_file_relapath, "hello world", rw_repo)
entries = index.reset(new_commit).add(
[BaseIndexEntry((0o10644, null_bin_sha, 0, new_file_relapath))],
fprogress=self._fprogress_add,
)
self._assert_entries(entries)
self._assert_fprogress(entries)
self.assertEqual(len(entries), 1)
self.assertNotEqual(entries[0].hexsha, null_hex_sha)
# Add symlink.
if sys.platform != "win32":
for target in ("/etc/nonexisting", "/etc/passwd", "/etc"):
basename = "my_real_symlink"
link_file = osp.join(rw_repo.working_tree_dir, basename)
> os.symlink(target, link_file)
E FileNotFoundError: [Errno 2] No such file or directory: '/etc/nonexisting' -> '/tmp/non_bare_test_index_mutationn7njifhq/my_real_symlink'
test/test_index.py:757: FileNotFoundError
------------------------------------------------- Captured stdout call -------------------------------------------------
1144447993 1112911993
test/test_index.py ⨯✓✓✓✓✓✓✓ 63% ██████▍
test/test_installation.py ✓ 63% ██████▍
test/test_quick_doc.py ✓✓ 63% ██████▍
test/test_reflog.py ✓✓ 64% ██████▍
test/test_refs.py ✓✓✓ 64% ██████▌
――――――――――――――――――――――――――――――――――――――――――――――― TestRefs.test_head_reset ―――――――――――――――――――――――――――――――――――――――――――――――
self = <test.test_refs.TestRefs testMethod=test_head_reset>
rw_repo = <git.repo.base.Repo '/tmp/non_bare_test_head_resetyp4jzgaj/.git'>
@with_rw_repo("0.1.6")
def test_head_reset(self, rw_repo):
cur_head = rw_repo.head
old_head_commit = cur_head.commit
new_head_commit = cur_head.ref.commit.parents[0]
cur_head.reset(new_head_commit, index=True) # index only
assert cur_head.reference.commit == new_head_commit
self.assertRaises(ValueError, cur_head.reset, new_head_commit, index=False, working_tree=True)
new_head_commit = new_head_commit.parents[0]
cur_head.reset(new_head_commit, index=True, working_tree=True) # index + wt
assert cur_head.reference.commit == new_head_commit
# Paths - make sure we have something to do.
rw_repo.index.reset(old_head_commit.parents[0])
cur_head.reset(cur_head, paths="test")
cur_head.reset(new_head_commit, paths="lib")
# Hard resets with paths don't work; it's all or nothing.
self.assertRaises(
GitCommandError,
cur_head.reset,
new_head_commit,
working_tree=True,
paths="lib",
)
# We can do a mixed reset, and then checkout from the index though.
cur_head.reset(new_head_commit)
rw_repo.index.checkout(["lib"], force=True)
# Now that we have a write write repo, change the HEAD reference - it's like
# "git-reset --soft".
heads = rw_repo.heads
assert heads
for head in heads:
cur_head.reference = head
assert cur_head.reference == head
assert isinstance(cur_head.reference, Head)
assert cur_head.commit == head.commit
assert not cur_head.is_detached
# END for each head
# Detach.
active_head = heads[0]
curhead_commit = active_head.commit
cur_head.reference = curhead_commit
assert cur_head.commit == curhead_commit
assert cur_head.is_detached
self.assertRaises(TypeError, getattr, cur_head, "reference")
# Tags are references, hence we can point to them.
some_tag = rw_repo.tags[0]
cur_head.reference = some_tag
assert not cur_head.is_detached
assert cur_head.commit == some_tag.commit
assert isinstance(cur_head.reference, TagReference)
# Put HEAD back to a real head, otherwise everything else fails.
cur_head.reference = active_head
# Type check.
self.assertRaises(ValueError, setattr, cur_head, "reference", "that")
# Head handling.
commit = "HEAD"
prev_head_commit = cur_head.commit
for count, new_name in enumerate(("my_new_head", "feature/feature1")):
actual_commit = commit + "^" * count
new_head = Head.create(rw_repo, new_name, actual_commit)
assert new_head.is_detached
assert cur_head.commit == prev_head_commit
assert isinstance(new_head, Head)
# Already exists, but has the same value, so it's fine.
Head.create(rw_repo, new_name, new_head.commit)
# It's not fine with a different value.
self.assertRaises(OSError, Head.create, rw_repo, new_name, new_head.commit.parents[0])
# Force it.
new_head = Head.create(rw_repo, new_name, actual_commit, force=True)
old_path = new_head.path
old_name = new_head.name
assert new_head.rename("hello").name == "hello"
assert new_head.rename("hello/world").name == "hello/world"
assert new_head.rename(old_name).name == old_name and new_head.path == old_path
# Rename with force.
tmp_head = Head.create(rw_repo, "tmphead")
self.assertRaises(GitCommandError, tmp_head.rename, new_head)
tmp_head.rename(new_head, force=True)
assert tmp_head == new_head and tmp_head.object == new_head.object
logfile = RefLog.path(tmp_head)
assert osp.isfile(logfile)
Head.delete(rw_repo, tmp_head)
# Deletion removes the log as well.
assert not osp.isfile(logfile)
heads = rw_repo.heads
assert tmp_head not in heads and new_head not in heads
# Force on deletion testing would be missing here, code looks okay though. ;)
# END for each new head name
self.assertRaises(TypeError, RemoteReference.create, rw_repo, "some_name")
# Tag ref.
tag_name = "5.0.2"
TagReference.create(rw_repo, tag_name)
self.assertRaises(GitCommandError, TagReference.create, rw_repo, tag_name)
light_tag = TagReference.create(rw_repo, tag_name, "HEAD~1", force=True)
assert isinstance(light_tag, TagReference)
assert light_tag.name == tag_name
assert light_tag.commit == cur_head.commit.parents[0]
assert light_tag.tag is None
# Tag with tag object.
other_tag_name = "releases/1.0.2RC"
msg = "my mighty tag\nsecond line"
> obj_tag = TagReference.create(rw_repo, other_tag_name, message=msg)
test/test_refs.py:336:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
git/refs/tag.py:145: in create
repo.git.tag(*args, **kwargs)
git/cmd.py:986: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git/cmd.py:1599: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x6ffffe238d60>
command = ['git', 'tag', '-m', 'my mighty tag\nsecond line', 'releases/1.0.2RC', 'HEAD'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'!C:': 'C:\\msys64\\home\\ek', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'AZURE_CLI_PATH': 'C:\\Users\\ek\\scoop\\apps\\azure-cli\\current\\bin', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
cwd = '/tmp/non_bare_test_head_resetyp4jzgaj', inline_env = None, cmd_not_found_exception = <class 'FileNotFoundError'>
stdout_sink = -1
communicate = <bound method Popen.communicate of <Popen: returncode: 128 args: ['git', 'tag', '-m', 'my mighty tag\nsecond li...>>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
R"""Handle executing the command, and consume and return the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to :class:`subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render `with_extended_output`
and `with_exceptions` ineffective - the caller will have to deal with
the details. It is important to note that the process will be placed
into an :class:`AutoInterrupt` wrapper that will interrupt the process
once it goes out of scope. If you use the command in iterators, you
should pass the whole process instance instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
copied to the given stream instead of being returned as a string.
This feature only has any effect if `as_process` is ``False``.
:param stdout_as_string:
If ``False``, the command's standard output will be bytes. Otherwise, it
will be decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if `as_process` is set to
``True``. It is set to ``None`` by default and will let the process run
until the timeout is explicitly specified. Uses of this feature should be
carefully considered, due to the following limitations:
1. This feature is not supported at all on Windows.
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
enumerate child processes, which is available on most GNU/Linux systems
but not most others.
3. Deeper descendants do not receive signals, though they may sometimes
terminate as a consequence of their parent processes being killed.
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
effects on a repository. For example, stale locks in case of
:manpage:`git-gc(1)` could render the repository incapable of accepting
changes until the lock is manually removed.
:param with_stdout:
If ``True``, default ``True``, we open stdout on the created process.
:param universal_newlines:
If ``True``, pipes will be opened as text, and lines are split at all known
line endings.
:param shell:
Whether to invoke commands through a shell
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
If this is not ``None``, it overrides :attr:`USE_SHELL`.
Passing ``shell=True`` to this or any other GitPython function should be
avoided, as it is unsafe under most circumstances. This is because it is
typically not feasible to fully consider and account for the effect of shell
expansions, especially when passing ``shell=True`` to other methods that
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
needed (nor useful) to work around any known operating system specific
issues.
:param env:
A dictionary of environment variables to be passed to
:class:`subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the `output_stream`
in one invocation of its ``write()`` method. If the given number is not
positive then the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
that some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output), if `extended_output` is ``False`` (Default)
* tuple(int(status), str(stdout), str(stderr)),
if `extended_output` is ``True``
If `output_stream` is ``True``, the stdout value will be your output stream:
* output_stream, if `extended_output` is ``False``
* tuple(int(status), output_stream, str(stderr)),
if `extended_output` is ``True``
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise git.exc.GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method, you
must update the ``execute_kwargs`` variable housed in this module.
"""
# Remove password for the command if present.
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process.
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ASCII English, which is what some parsing
# code may expect.
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell = super().__getattribute__("USE_SHELL")
_logger.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
proc = safer_popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=(istream or DEVNULL),
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
universal_newlines=universal_newlines,
encoding=defenc if universal_newlines else None,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# Replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
if sys.platform != "win32" and kill_after_timeout is not None:
# Help mypy figure out this is not None even when used inside communicate().
timeout = kill_after_timeout
def kill_process(pid: int) -> None:
"""Callback to kill a process.
This callback implementation would be ineffective and unsafe on Windows.
"""
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
os.kill(pid, signal.SIGKILL)
for child_pid in child_pids:
try:
os.kill(child_pid, signal.SIGKILL)
except OSError:
pass
# Tell the main routine that the process was killed.
kill_check.set()
except OSError:
# It is possible that the process gets completed in the duration
# after timeout happens and before we try to kill the process.
pass
return
def communicate() -> Tuple[AnyStr, AnyStr]:
watchdog.start()
out, err = proc.communicate()
watchdog.cancel()
if kill_check.is_set():
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
timeout,
)
if not universal_newlines:
err = err.encode(defenc)
return out, err
# END helpers
kill_check = threading.Event()
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
else:
communicate = proc.communicate
# Wait for the process to return.
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
stdout_value, stderr_value = communicate()
# Strip trailing "\n".
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# END as_text
if stderr_value:
_logger.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
_logger.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git tag -m my mighty tag
E second line releases/1.0.2RC HEAD
E stderr: 'Committer identity unknown
E
E *** Please tell me who you are.
E
E Run
E
E git config --global user.email "[email protected]"
E git config --global user.name "Your Name"
E
E to set your account's default identity.
E Omit --global to set the identity only in this repository.
E
E fatal: unable to auto-detect email address (got 'ek@Glub.(none)')'
git/cmd.py:1389: GitCommandError
test/test_refs.py ⨯✓✓✓✓✓✓✓✓ 65% ██████▋
―――――――――――――――――――――――――――――――――――――――――――――― TestRefs.test_tag_message ―――――――――――――――――――――――――――――――――――――――――――――――
self = <test.test_refs.TestRefs testMethod=test_tag_message>
rw_repo = <git.repo.base.Repo '/tmp/non_bare_test_tag_messagelnb_nwb6/.git'>
@with_rw_repo("0.1.6")
def test_tag_message(self, rw_repo):
> tag_ref = TagReference.create(rw_repo, "test-message-1", message="test")
test/test_refs.py:598:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
git/refs/tag.py:145: in create
repo.git.tag(*args, **kwargs)
git/cmd.py:986: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git/cmd.py:1599: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x6ffffe17b1c0>, command = ['git', 'tag', '-m', 'test', 'test-message-1', 'HEAD']
istream = None, with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None
stdout_as_string = True, kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'!C:': 'C:\\msys64\\home\\ek', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'AZURE_CLI_PATH': 'C:\\Users\\ek\\scoop\\apps\\azure-cli\\current\\bin', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
cwd = '/tmp/non_bare_test_tag_messagelnb_nwb6', inline_env = None, cmd_not_found_exception = <class 'FileNotFoundError'>
stdout_sink = -1
communicate = <bound method Popen.communicate of <Popen: returncode: 128 args: ['git', 'tag', '-m', 'test', 'test-message-1',...>>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
R"""Handle executing the command, and consume and return the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to :class:`subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render `with_extended_output`
and `with_exceptions` ineffective - the caller will have to deal with
the details. It is important to note that the process will be placed
into an :class:`AutoInterrupt` wrapper that will interrupt the process
once it goes out of scope. If you use the command in iterators, you
should pass the whole process instance instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
copied to the given stream instead of being returned as a string.
This feature only has any effect if `as_process` is ``False``.
:param stdout_as_string:
If ``False``, the command's standard output will be bytes. Otherwise, it
will be decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if `as_process` is set to
``True``. It is set to ``None`` by default and will let the process run
until the timeout is explicitly specified. Uses of this feature should be
carefully considered, due to the following limitations:
1. This feature is not supported at all on Windows.
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
enumerate child processes, which is available on most GNU/Linux systems
but not most others.
3. Deeper descendants do not receive signals, though they may sometimes
terminate as a consequence of their parent processes being killed.
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
effects on a repository. For example, stale locks in case of
:manpage:`git-gc(1)` could render the repository incapable of accepting
changes until the lock is manually removed.
:param with_stdout:
If ``True``, default ``True``, we open stdout on the created process.
:param universal_newlines:
If ``True``, pipes will be opened as text, and lines are split at all known
line endings.
:param shell:
Whether to invoke commands through a shell
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
If this is not ``None``, it overrides :attr:`USE_SHELL`.
Passing ``shell=True`` to this or any other GitPython function should be
avoided, as it is unsafe under most circumstances. This is because it is
typically not feasible to fully consider and account for the effect of shell
expansions, especially when passing ``shell=True`` to other methods that
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
needed (nor useful) to work around any known operating system specific
issues.
:param env:
A dictionary of environment variables to be passed to
:class:`subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the `output_stream`
in one invocation of its ``write()`` method. If the given number is not
positive then the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
that some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output), if `extended_output` is ``False`` (Default)
* tuple(int(status), str(stdout), str(stderr)),
if `extended_output` is ``True``
If `output_stream` is ``True``, the stdout value will be your output stream:
* output_stream, if `extended_output` is ``False``
* tuple(int(status), output_stream, str(stderr)),
if `extended_output` is ``True``
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise git.exc.GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method, you
must update the ``execute_kwargs`` variable housed in this module.
"""
# Remove password for the command if present.
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process.
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ASCII English, which is what some parsing
# code may expect.
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell = super().__getattribute__("USE_SHELL")
_logger.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
proc = safer_popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=(istream or DEVNULL),
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
universal_newlines=universal_newlines,
encoding=defenc if universal_newlines else None,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# Replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
if sys.platform != "win32" and kill_after_timeout is not None:
# Help mypy figure out this is not None even when used inside communicate().
timeout = kill_after_timeout
def kill_process(pid: int) -> None:
"""Callback to kill a process.
This callback implementation would be ineffective and unsafe on Windows.
"""
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
os.kill(pid, signal.SIGKILL)
for child_pid in child_pids:
try:
os.kill(child_pid, signal.SIGKILL)
except OSError:
pass
# Tell the main routine that the process was killed.
kill_check.set()
except OSError:
# It is possible that the process gets completed in the duration
# after timeout happens and before we try to kill the process.
pass
return
def communicate() -> Tuple[AnyStr, AnyStr]:
watchdog.start()
out, err = proc.communicate()
watchdog.cancel()
if kill_check.is_set():
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
timeout,
)
if not universal_newlines:
err = err.encode(defenc)
return out, err
# END helpers
kill_check = threading.Event()
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
else:
communicate = proc.communicate
# Wait for the process to return.
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
stdout_value, stderr_value = communicate()
# Strip trailing "\n".
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# END as_text
if stderr_value:
_logger.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
_logger.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git tag -m test test-message-1 HEAD
E stderr: 'Committer identity unknown
E
E *** Please tell me who you are.
E
E Run
E
E git config --global user.email "[email protected]"
E git config --global user.name "Your Name"
E
E to set your account's default identity.
E Omit --global to set the identity only in this repository.
E
E fatal: unable to auto-detect email address (got 'ek@Glub.(none)')'
git/cmd.py:1389: GitCommandError
test/test_refs.py ⨯✓✓✓ 66% ██████▋
test/test_remote.py ✓✓ 66% ██████▋
――――――――――――――――――――――――――――――――――――――――――――――――― TestRemote.test_base ―――――――――――――――――――――――――――――――――――――――――――――――――
self = <test.test_remote.TestRemote testMethod=test_base>
rw_repo = <git.repo.base.Repo '/tmp/daemon_cloned_repo-test_base-0asbyonx/.git'>
remote_repo = <git.repo.base.Repo '/tmp/daemon_repo-test_base-2pd0igsn'>
@skipIf(HIDE_WINDOWS_FREEZE_ERRORS, "FIXME: Freezes!")
@with_rw_and_rw_remote_repo("0.1.6")
def test_base(self, rw_repo, remote_repo):
num_remotes = 0
remote_set = set()
ran_fetch_test = False
for remote in rw_repo.remotes:
num_remotes += 1
self.assertEqual(remote, remote)
self.assertNotEqual(str(remote), repr(remote))
remote_set.add(remote)
remote_set.add(remote) # Should already exist.
# REFS
refs = remote.refs
self.assertTrue(refs)
for ref in refs:
self.assertEqual(ref.remote_name, remote.name)
self.assertTrue(ref.remote_head)
# END for each ref
# OPTIONS
# Cannot use 'fetch' key anymore as it is now a method.
for opt in ("url",):
val = getattr(remote, opt)
reader = remote.config_reader
assert reader.get(opt) == val
assert reader.get_value(opt, None) == val
# Unable to write with a reader.
self.assertRaises(IOError, reader.set, opt, "test")
# Change value.
with remote.config_writer as writer:
new_val = "myval"
writer.set(opt, new_val)
assert writer.get(opt) == new_val
writer.set(opt, val)
assert writer.get(opt) == val
assert getattr(remote, opt) == val
# END for each default option key
# RENAME
other_name = "totally_other_name"
prev_name = remote.name
self.assertEqual(remote.rename(other_name), remote)
self.assertNotEqual(prev_name, remote.name)
# Multiple times.
for _ in range(2):
self.assertEqual(remote.rename(prev_name).name, prev_name)
# END for each rename ( back to prev_name )
# PUSH/PULL TESTING
> self._assert_push_and_pull(remote, rw_repo, remote_repo)
test/test_remote.py:487:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
test/test_remote.py:373: in _assert_push_and_pull
other_tag = TagReference.create(rw_repo, "my_obj_tag.2.1aRV", logmsg="my message")
git/refs/tag.py:145: in create
repo.git.tag(*args, **kwargs)
git/cmd.py:986: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git/cmd.py:1599: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x6ffffe90efe0>, command = ['git', 'tag', '-m', 'my message', 'my_obj_tag.2.1aRV', 'HEAD']
istream = None, with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None
stdout_as_string = True, kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'!C:': 'C:\\msys64\\home\\ek', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'AZURE_CLI_PATH': 'C:\\Users\\ek\\scoop\\apps\\azure-cli\\current\\bin', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
cwd = '/tmp/daemon_cloned_repo-test_base-0asbyonx', inline_env = None
cmd_not_found_exception = <class 'FileNotFoundError'>, stdout_sink = -1
communicate = <bound method Popen.communicate of <Popen: returncode: 128 args: ['git', 'tag', '-m', 'my message', 'my_obj_tag...>>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
R"""Handle executing the command, and consume and return the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to :class:`subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render `with_extended_output`
and `with_exceptions` ineffective - the caller will have to deal with
the details. It is important to note that the process will be placed
into an :class:`AutoInterrupt` wrapper that will interrupt the process
once it goes out of scope. If you use the command in iterators, you
should pass the whole process instance instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
copied to the given stream instead of being returned as a string.
This feature only has any effect if `as_process` is ``False``.
:param stdout_as_string:
If ``False``, the command's standard output will be bytes. Otherwise, it
will be decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if `as_process` is set to
``True``. It is set to ``None`` by default and will let the process run
until the timeout is explicitly specified. Uses of this feature should be
carefully considered, due to the following limitations:
1. This feature is not supported at all on Windows.
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
enumerate child processes, which is available on most GNU/Linux systems
but not most others.
3. Deeper descendants do not receive signals, though they may sometimes
terminate as a consequence of their parent processes being killed.
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
effects on a repository. For example, stale locks in case of
:manpage:`git-gc(1)` could render the repository incapable of accepting
changes until the lock is manually removed.
:param with_stdout:
If ``True``, default ``True``, we open stdout on the created process.
:param universal_newlines:
If ``True``, pipes will be opened as text, and lines are split at all known
line endings.
:param shell:
Whether to invoke commands through a shell
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
If this is not ``None``, it overrides :attr:`USE_SHELL`.
Passing ``shell=True`` to this or any other GitPython function should be
avoided, as it is unsafe under most circumstances. This is because it is
typically not feasible to fully consider and account for the effect of shell
expansions, especially when passing ``shell=True`` to other methods that
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
needed (nor useful) to work around any known operating system specific
issues.
:param env:
A dictionary of environment variables to be passed to
:class:`subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the `output_stream`
in one invocation of its ``write()`` method. If the given number is not
positive then the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
that some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output), if `extended_output` is ``False`` (Default)
* tuple(int(status), str(stdout), str(stderr)),
if `extended_output` is ``True``
If `output_stream` is ``True``, the stdout value will be your output stream:
* output_stream, if `extended_output` is ``False``
* tuple(int(status), output_stream, str(stderr)),
if `extended_output` is ``True``
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise git.exc.GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method, you
must update the ``execute_kwargs`` variable housed in this module.
"""
# Remove password for the command if present.
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process.
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ASCII English, which is what some parsing
# code may expect.
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell = super().__getattribute__("USE_SHELL")
_logger.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
proc = safer_popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=(istream or DEVNULL),
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
universal_newlines=universal_newlines,
encoding=defenc if universal_newlines else None,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# Replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
if sys.platform != "win32" and kill_after_timeout is not None:
# Help mypy figure out this is not None even when used inside communicate().
timeout = kill_after_timeout
def kill_process(pid: int) -> None:
"""Callback to kill a process.
This callback implementation would be ineffective and unsafe on Windows.
"""
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
os.kill(pid, signal.SIGKILL)
for child_pid in child_pids:
try:
os.kill(child_pid, signal.SIGKILL)
except OSError:
pass
# Tell the main routine that the process was killed.
kill_check.set()
except OSError:
# It is possible that the process gets completed in the duration
# after timeout happens and before we try to kill the process.
pass
return
def communicate() -> Tuple[AnyStr, AnyStr]:
watchdog.start()
out, err = proc.communicate()
watchdog.cancel()
if kill_check.is_set():
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
timeout,
)
if not universal_newlines:
err = err.encode(defenc)
return out, err
# END helpers
kill_check = threading.Event()
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
else:
communicate = proc.communicate
# Wait for the process to return.
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
stdout_value, stderr_value = communicate()
# Strip trailing "\n".
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# END as_text
if stderr_value:
_logger.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
_logger.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git tag -m my message my_obj_tag.2.1aRV HEAD
E stderr: 'Committer identity unknown
E
E *** Please tell me who you are.
E
E Run
E
E git config --global user.email "[email protected]"
E git config --global user.name "Your Name"
E
E to set your account's default identity.
E Omit --global to set the identity only in this repository.
E
E fatal: unable to auto-detect email address (got 'ek@Glub.(none)')'
git/cmd.py:1389: GitCommandError
-------------------------------------------------- Captured log call ---------------------------------------------------
WARNING git.remote:remote.py:982 Error lines received while fetching: error: failed to push some refs to '/tmp/daemon_repo-test_base-2pd0igsn'
test/test_remote.py ⨯✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 70% ███████
test/test_repo.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 75% ███████▌
――――――――――――――――――――――――――――――――――――― TestRepo.test_do_not_strip_newline_in_stdout ―――――――――――――――――――――――――――――――――――――
self = <test.test_repo.TestRepo testMethod=test_do_not_strip_newline_in_stdout>
rw_dir = '/tmp/test_do_not_strip_newline_in_stdoutje99599m'
@with_rw_directory
def test_do_not_strip_newline_in_stdout(self, rw_dir):
r = Repo.init(rw_dir)
fp = osp.join(rw_dir, "hello.txt")
with open(fp, "w") as fs:
fs.write("hello\n")
r.git.add(Git.polish_url(fp))
> r.git.commit(message="init")
test/test_repo.py:1385:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
git/cmd.py:986: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git/cmd.py:1599: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x6ffffe77cac0>, command = ['git', 'commit', '--message=init'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'!C:': 'C:\\msys64\\home\\ek', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'AZURE_CLI_PATH': 'C:\\Users\\ek\\scoop\\apps\\azure-cli\\current\\bin', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
cwd = '/tmp/test_do_not_strip_newline_in_stdoutje99599m', inline_env = None
cmd_not_found_exception = <class 'FileNotFoundError'>, stdout_sink = -1
communicate = <bound method Popen.communicate of <Popen: returncode: 128 args: ['git', 'commit', '--message=init']>>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
R"""Handle executing the command, and consume and return the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to :class:`subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render `with_extended_output`
and `with_exceptions` ineffective - the caller will have to deal with
the details. It is important to note that the process will be placed
into an :class:`AutoInterrupt` wrapper that will interrupt the process
once it goes out of scope. If you use the command in iterators, you
should pass the whole process instance instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
copied to the given stream instead of being returned as a string.
This feature only has any effect if `as_process` is ``False``.
:param stdout_as_string:
If ``False``, the command's standard output will be bytes. Otherwise, it
will be decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if `as_process` is set to
``True``. It is set to ``None`` by default and will let the process run
until the timeout is explicitly specified. Uses of this feature should be
carefully considered, due to the following limitations:
1. This feature is not supported at all on Windows.
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
enumerate child processes, which is available on most GNU/Linux systems
but not most others.
3. Deeper descendants do not receive signals, though they may sometimes
terminate as a consequence of their parent processes being killed.
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
effects on a repository. For example, stale locks in case of
:manpage:`git-gc(1)` could render the repository incapable of accepting
changes until the lock is manually removed.
:param with_stdout:
If ``True``, default ``True``, we open stdout on the created process.
:param universal_newlines:
If ``True``, pipes will be opened as text, and lines are split at all known
line endings.
:param shell:
Whether to invoke commands through a shell
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
If this is not ``None``, it overrides :attr:`USE_SHELL`.
Passing ``shell=True`` to this or any other GitPython function should be
avoided, as it is unsafe under most circumstances. This is because it is
typically not feasible to fully consider and account for the effect of shell
expansions, especially when passing ``shell=True`` to other methods that
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
needed (nor useful) to work around any known operating system specific
issues.
:param env:
A dictionary of environment variables to be passed to
:class:`subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the `output_stream`
in one invocation of its ``write()`` method. If the given number is not
positive then the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
that some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output), if `extended_output` is ``False`` (Default)
* tuple(int(status), str(stdout), str(stderr)),
if `extended_output` is ``True``
If `output_stream` is ``True``, the stdout value will be your output stream:
* output_stream, if `extended_output` is ``False``
* tuple(int(status), output_stream, str(stderr)),
if `extended_output` is ``True``
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise git.exc.GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method, you
must update the ``execute_kwargs`` variable housed in this module.
"""
# Remove password for the command if present.
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process.
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ASCII English, which is what some parsing
# code may expect.
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell = super().__getattribute__("USE_SHELL")
_logger.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
proc = safer_popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=(istream or DEVNULL),
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
universal_newlines=universal_newlines,
encoding=defenc if universal_newlines else None,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# Replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
if sys.platform != "win32" and kill_after_timeout is not None:
# Help mypy figure out this is not None even when used inside communicate().
timeout = kill_after_timeout
def kill_process(pid: int) -> None:
"""Callback to kill a process.
This callback implementation would be ineffective and unsafe on Windows.
"""
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
os.kill(pid, signal.SIGKILL)
for child_pid in child_pids:
try:
os.kill(child_pid, signal.SIGKILL)
except OSError:
pass
# Tell the main routine that the process was killed.
kill_check.set()
except OSError:
# It is possible that the process gets completed in the duration
# after timeout happens and before we try to kill the process.
pass
return
def communicate() -> Tuple[AnyStr, AnyStr]:
watchdog.start()
out, err = proc.communicate()
watchdog.cancel()
if kill_check.is_set():
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
timeout,
)
if not universal_newlines:
err = err.encode(defenc)
return out, err
# END helpers
kill_check = threading.Event()
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
else:
communicate = proc.communicate
# Wait for the process to return.
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
stdout_value, stderr_value = communicate()
# Strip trailing "\n".
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# END as_text
if stderr_value:
_logger.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
_logger.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git commit --message=init
E stderr: 'Author identity unknown
E
E *** Please tell me who you are.
E
E Run
E
E git config --global user.email "[email protected]"
E git config --global user.name "Your Name"
E
E to set your account's default identity.
E Omit --global to set the identity only in this repository.
E
E fatal: unable to auto-detect email address (got 'ek@Glub.(none)')'
git/cmd.py:1389: GitCommandError
test/test_repo.py ⨯✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓s✓✓✓✓✓✓ 79% ███████▉
―――――――――――――――――――――――――――――――――――――――――――――――― TestRepo.test_rebasing ――――――――――――――――――――――――――――――――――――――――――――――――
self = <test.test_repo.TestRepo testMethod=test_rebasing>, rw_dir = '/tmp/test_rebasingfk27v3tb'
@with_rw_directory
def test_rebasing(self, rw_dir):
r = Repo.init(rw_dir)
fp = osp.join(rw_dir, "hello.txt")
> r.git.commit(
"--allow-empty",
message="init",
)
test/test_repo.py:1357:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
git/cmd.py:986: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git/cmd.py:1599: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x6ffffe17ba00>, command = ['git', 'commit', '--message=init', '--allow-empty']
istream = None, with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None
stdout_as_string = True, kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'!C:': 'C:\\msys64\\home\\ek', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'AZURE_CLI_PATH': 'C:\\Users\\ek\\scoop\\apps\\azure-cli\\current\\bin', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}, cwd = '/tmp/test_rebasingfk27v3tb'
inline_env = None, cmd_not_found_exception = <class 'FileNotFoundError'>, stdout_sink = -1
communicate = <bound method Popen.communicate of <Popen: returncode: 128 args: ['git', 'commit', '--message=init', '--allow-e...>>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
R"""Handle executing the command, and consume and return the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to :class:`subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render `with_extended_output`
and `with_exceptions` ineffective - the caller will have to deal with
the details. It is important to note that the process will be placed
into an :class:`AutoInterrupt` wrapper that will interrupt the process
once it goes out of scope. If you use the command in iterators, you
should pass the whole process instance instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
copied to the given stream instead of being returned as a string.
This feature only has any effect if `as_process` is ``False``.
:param stdout_as_string:
If ``False``, the command's standard output will be bytes. Otherwise, it
will be decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if `as_process` is set to
``True``. It is set to ``None`` by default and will let the process run
until the timeout is explicitly specified. Uses of this feature should be
carefully considered, due to the following limitations:
1. This feature is not supported at all on Windows.
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
enumerate child processes, which is available on most GNU/Linux systems
but not most others.
3. Deeper descendants do not receive signals, though they may sometimes
terminate as a consequence of their parent processes being killed.
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
effects on a repository. For example, stale locks in case of
:manpage:`git-gc(1)` could render the repository incapable of accepting
changes until the lock is manually removed.
:param with_stdout:
If ``True``, default ``True``, we open stdout on the created process.
:param universal_newlines:
If ``True``, pipes will be opened as text, and lines are split at all known
line endings.
:param shell:
Whether to invoke commands through a shell
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
If this is not ``None``, it overrides :attr:`USE_SHELL`.
Passing ``shell=True`` to this or any other GitPython function should be
avoided, as it is unsafe under most circumstances. This is because it is
typically not feasible to fully consider and account for the effect of shell
expansions, especially when passing ``shell=True`` to other methods that
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
needed (nor useful) to work around any known operating system specific
issues.
:param env:
A dictionary of environment variables to be passed to
:class:`subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the `output_stream`
in one invocation of its ``write()`` method. If the given number is not
positive then the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
that some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output), if `extended_output` is ``False`` (Default)
* tuple(int(status), str(stdout), str(stderr)),
if `extended_output` is ``True``
If `output_stream` is ``True``, the stdout value will be your output stream:
* output_stream, if `extended_output` is ``False``
* tuple(int(status), output_stream, str(stderr)),
if `extended_output` is ``True``
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise git.exc.GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method, you
must update the ``execute_kwargs`` variable housed in this module.
"""
# Remove password for the command if present.
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process.
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ASCII English, which is what some parsing
# code may expect.
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell = super().__getattribute__("USE_SHELL")
_logger.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
proc = safer_popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=(istream or DEVNULL),
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
universal_newlines=universal_newlines,
encoding=defenc if universal_newlines else None,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# Replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
if sys.platform != "win32" and kill_after_timeout is not None:
# Help mypy figure out this is not None even when used inside communicate().
timeout = kill_after_timeout
def kill_process(pid: int) -> None:
"""Callback to kill a process.
This callback implementation would be ineffective and unsafe on Windows.
"""
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
os.kill(pid, signal.SIGKILL)
for child_pid in child_pids:
try:
os.kill(child_pid, signal.SIGKILL)
except OSError:
pass
# Tell the main routine that the process was killed.
kill_check.set()
except OSError:
# It is possible that the process gets completed in the duration
# after timeout happens and before we try to kill the process.
pass
return
def communicate() -> Tuple[AnyStr, AnyStr]:
watchdog.start()
out, err = proc.communicate()
watchdog.cancel()
if kill_check.is_set():
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
timeout,
)
if not universal_newlines:
err = err.encode(defenc)
return out, err
# END helpers
kill_check = threading.Event()
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
else:
communicate = proc.communicate
# Wait for the process to return.
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
stdout_value, stderr_value = communicate()
# Strip trailing "\n".
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# END as_text
if stderr_value:
_logger.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
_logger.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git commit --message=init --allow-empty
E stderr: 'Author identity unknown
E
E *** Please tell me who you are.
E
E Run
E
E git config --global user.email "[email protected]"
E git config --global user.name "Your Name"
E
E to set your account's default identity.
E Omit --global to set the identity only in this repository.
E
E fatal: unable to auto-detect email address (got 'ek@Glub.(none)')'
git/cmd.py:1389: GitCommandError
test/test_repo.py ⨯✓✓✓✓✓✓✓✓✓X✓✓✓✓✓✓ 81% ████████▎
test/test_stats.py ✓ 82% ████████▎
test/test_submodule.py ✓✓✓✓✓✓x✓✓ 83% ████████▍
――――――――――――――――――――――――――――― TestSubmodule.test_git_submodules_and_add_sm_with_new_commit ―――――――――――――――――――――――――――――
self = <test.test_submodule.TestSubmodule testMethod=test_git_submodules_and_add_sm_with_new_commit>
rwdir = '/tmp/test_git_submodules_and_add_sm_with_new_commit1i40xz2u'
@pytest.mark.xfail(
HIDE_WINDOWS_KNOWN_ERRORS,
reason=(
'"The process cannot access the file because it is being used by another process"'
+ " on first call to sm.move"
),
raises=PermissionError,
)
@with_rw_directory
@_patch_git_config("protocol.file.allow", "always")
def test_git_submodules_and_add_sm_with_new_commit(self, rwdir):
parent = git.Repo.init(osp.join(rwdir, "parent"))
parent.git.submodule("add", self._small_repo_url(), "module")
parent.index.commit("added submodule")
assert len(parent.submodules) == 1
sm = parent.submodules[0]
assert sm.exists() and sm.module_exists()
clone = git.Repo.clone_from(
self._small_repo_url(),
osp.join(parent.working_tree_dir, "existing-subrepository"),
)
sm2 = parent.create_submodule("nongit-file-submodule", clone.working_tree_dir)
assert len(parent.submodules) == 2
for _ in range(2):
for init in (False, True):
sm.update(init=init)
sm2.update(init=init)
# END for each init state
# END for each iteration
sm.move(sm.path + "_moved")
sm2.move(sm2.path + "_moved")
parent.index.commit("moved submodules")
with sm.config_writer() as writer:
writer.set_value("user.email", "[email protected]")
writer.set_value("user.name", "me")
smm = sm.module()
fp = osp.join(smm.working_tree_dir, "empty-file")
with open(fp, "w"):
pass
smm.git.add(Git.polish_url(fp))
> smm.git.commit(m="new file added")
test/test_submodule.py:822:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
git/cmd.py:986: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git/cmd.py:1599: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x6ffffe6c0c40>, command = ['git', 'commit', '-m', 'new file added'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'!C:': 'C:\\msys64\\home\\ek', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'AZURE_CLI_PATH': 'C:\\Users\\ek\\scoop\\apps\\azure-cli\\current\\bin', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
cwd = '/tmp/test_git_submodules_and_add_sm_with_new_commit1i40xz2u/parent/module_moved', inline_env = None
cmd_not_found_exception = <class 'FileNotFoundError'>, stdout_sink = -1
communicate = <bound method Popen.communicate of <Popen: returncode: 128 args: ['git', 'commit', '-m', 'new file added']>>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
R"""Handle executing the command, and consume and return the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to :class:`subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render `with_extended_output`
and `with_exceptions` ineffective - the caller will have to deal with
the details. It is important to note that the process will be placed
into an :class:`AutoInterrupt` wrapper that will interrupt the process
once it goes out of scope. If you use the command in iterators, you
should pass the whole process instance instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
copied to the given stream instead of being returned as a string.
This feature only has any effect if `as_process` is ``False``.
:param stdout_as_string:
If ``False``, the command's standard output will be bytes. Otherwise, it
will be decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if `as_process` is set to
``True``. It is set to ``None`` by default and will let the process run
until the timeout is explicitly specified. Uses of this feature should be
carefully considered, due to the following limitations:
1. This feature is not supported at all on Windows.
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
enumerate child processes, which is available on most GNU/Linux systems
but not most others.
3. Deeper descendants do not receive signals, though they may sometimes
terminate as a consequence of their parent processes being killed.
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
effects on a repository. For example, stale locks in case of
:manpage:`git-gc(1)` could render the repository incapable of accepting
changes until the lock is manually removed.
:param with_stdout:
If ``True``, default ``True``, we open stdout on the created process.
:param universal_newlines:
If ``True``, pipes will be opened as text, and lines are split at all known
line endings.
:param shell:
Whether to invoke commands through a shell
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
If this is not ``None``, it overrides :attr:`USE_SHELL`.
Passing ``shell=True`` to this or any other GitPython function should be
avoided, as it is unsafe under most circumstances. This is because it is
typically not feasible to fully consider and account for the effect of shell
expansions, especially when passing ``shell=True`` to other methods that
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
needed (nor useful) to work around any known operating system specific
issues.
:param env:
A dictionary of environment variables to be passed to
:class:`subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the `output_stream`
in one invocation of its ``write()`` method. If the given number is not
positive then the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
that some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output), if `extended_output` is ``False`` (Default)
* tuple(int(status), str(stdout), str(stderr)),
if `extended_output` is ``True``
If `output_stream` is ``True``, the stdout value will be your output stream:
* output_stream, if `extended_output` is ``False``
* tuple(int(status), output_stream, str(stderr)),
if `extended_output` is ``True``
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise git.exc.GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method, you
must update the ``execute_kwargs`` variable housed in this module.
"""
# Remove password for the command if present.
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process.
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ASCII English, which is what some parsing
# code may expect.
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell = super().__getattribute__("USE_SHELL")
_logger.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
proc = safer_popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=(istream or DEVNULL),
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
universal_newlines=universal_newlines,
encoding=defenc if universal_newlines else None,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# Replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
if sys.platform != "win32" and kill_after_timeout is not None:
# Help mypy figure out this is not None even when used inside communicate().
timeout = kill_after_timeout
def kill_process(pid: int) -> None:
"""Callback to kill a process.
This callback implementation would be ineffective and unsafe on Windows.
"""
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
os.kill(pid, signal.SIGKILL)
for child_pid in child_pids:
try:
os.kill(child_pid, signal.SIGKILL)
except OSError:
pass
# Tell the main routine that the process was killed.
kill_check.set()
except OSError:
# It is possible that the process gets completed in the duration
# after timeout happens and before we try to kill the process.
pass
return
def communicate() -> Tuple[AnyStr, AnyStr]:
watchdog.start()
out, err = proc.communicate()
watchdog.cancel()
if kill_check.is_set():
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
timeout,
)
if not universal_newlines:
err = err.encode(defenc)
return out, err
# END helpers
kill_check = threading.Event()
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
else:
communicate = proc.communicate
# Wait for the process to return.
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
stdout_value, stderr_value = communicate()
# Strip trailing "\n".
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# END as_text
if stderr_value:
_logger.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
_logger.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git commit -m new file added
E stderr: 'Author identity unknown
E
E *** Please tell me who you are.
E
E Run
E
E git config --global user.email "[email protected]"
E git config --global user.name "Your Name"
E
E to set your account's default identity.
E Omit --global to set the identity only in this repository.
E
E fatal: unable to auto-detect email address (got 'ek@Glub.(none)')'
git/cmd.py:1389: GitCommandError
test/test_submodule.py ⨯ 83% ████████▍
――――――――――――――――――――――――――――――――――――― TestSubmodule.test_ignore_non_submodule_file ―――――――――――――――――――――――――――――――――――――
self = <test.test_submodule.TestSubmodule testMethod=test_ignore_non_submodule_file>
rwdir = '/tmp/test_ignore_non_submodule_filez8dqlht_'
@with_rw_directory
def test_ignore_non_submodule_file(self, rwdir):
parent = git.Repo.init(rwdir)
smp = osp.join(rwdir, "module")
os.mkdir(smp)
with open(osp.join(smp, "a"), "w", encoding="utf-8") as f:
f.write("test\n")
with open(osp.join(rwdir, ".gitmodules"), "w", encoding="utf-8") as f:
f.write('[submodule "a"]\n')
f.write(" path = module\n")
f.write(" url = https://github.com/chaconinc/DbConnector\n")
parent.git.add(Git.polish_url(osp.join(smp, "a")))
parent.git.add(Git.polish_url(osp.join(rwdir, ".gitmodules")))
> parent.git.commit(message="test")
test/test_submodule.py:956:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
git/cmd.py:986: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git/cmd.py:1599: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x6ffffe8a4a00>, command = ['git', 'commit', '--message=test'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'!C:': 'C:\\msys64\\home\\ek', 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'AZURE_CLI_PATH': 'C:\\Users\\ek\\scoop\\apps\\azure-cli\\current\\bin', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
cwd = '/tmp/test_ignore_non_submodule_filez8dqlht_', inline_env = None
cmd_not_found_exception = <class 'FileNotFoundError'>, stdout_sink = -1
communicate = <bound method Popen.communicate of <Popen: returncode: 128 args: ['git', 'commit', '--message=test']>>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
R"""Handle executing the command, and consume and return the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to :class:`subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render `with_extended_output`
and `with_exceptions` ineffective - the caller will have to deal with
the details. It is important to note that the process will be placed
into an :class:`AutoInterrupt` wrapper that will interrupt the process
once it goes out of scope. If you use the command in iterators, you
should pass the whole process instance instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
copied to the given stream instead of being returned as a string.
This feature only has any effect if `as_process` is ``False``.
:param stdout_as_string:
If ``False``, the command's standard output will be bytes. Otherwise, it
will be decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if `as_process` is set to
``True``. It is set to ``None`` by default and will let the process run
until the timeout is explicitly specified. Uses of this feature should be
carefully considered, due to the following limitations:
1. This feature is not supported at all on Windows.
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
enumerate child processes, which is available on most GNU/Linux systems
but not most others.
3. Deeper descendants do not receive signals, though they may sometimes
terminate as a consequence of their parent processes being killed.
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
effects on a repository. For example, stale locks in case of
:manpage:`git-gc(1)` could render the repository incapable of accepting
changes until the lock is manually removed.
:param with_stdout:
If ``True``, default ``True``, we open stdout on the created process.
:param universal_newlines:
If ``True``, pipes will be opened as text, and lines are split at all known
line endings.
:param shell:
Whether to invoke commands through a shell
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
If this is not ``None``, it overrides :attr:`USE_SHELL`.
Passing ``shell=True`` to this or any other GitPython function should be
avoided, as it is unsafe under most circumstances. This is because it is
typically not feasible to fully consider and account for the effect of shell
expansions, especially when passing ``shell=True`` to other methods that
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
needed (nor useful) to work around any known operating system specific
issues.
:param env:
A dictionary of environment variables to be passed to
:class:`subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the `output_stream`
in one invocation of its ``write()`` method. If the given number is not
positive then the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
that some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output), if `extended_output` is ``False`` (Default)
* tuple(int(status), str(stdout), str(stderr)),
if `extended_output` is ``True``
If `output_stream` is ``True``, the stdout value will be your output stream:
* output_stream, if `extended_output` is ``False``
* tuple(int(status), output_stream, str(stderr)),
if `extended_output` is ``True``
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise git.exc.GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method, you
must update the ``execute_kwargs`` variable housed in this module.
"""
# Remove password for the command if present.
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process.
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ASCII English, which is what some parsing
# code may expect.
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell = super().__getattribute__("USE_SHELL")
_logger.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
proc = safer_popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=(istream or DEVNULL),
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
universal_newlines=universal_newlines,
encoding=defenc if universal_newlines else None,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# Replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
if sys.platform != "win32" and kill_after_timeout is not None:
# Help mypy figure out this is not None even when used inside communicate().
timeout = kill_after_timeout
def kill_process(pid: int) -> None:
"""Callback to kill a process.
This callback implementation would be ineffective and unsafe on Windows.
"""
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
os.kill(pid, signal.SIGKILL)
for child_pid in child_pids:
try:
os.kill(child_pid, signal.SIGKILL)
except OSError:
pass
# Tell the main routine that the process was killed.
kill_check.set()
except OSError:
# It is possible that the process gets completed in the duration
# after timeout happens and before we try to kill the process.
pass
return
def communicate() -> Tuple[AnyStr, AnyStr]:
watchdog.start()
out, err = proc.communicate()
watchdog.cancel()
if kill_check.is_set():
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
timeout,
)
if not universal_newlines:
err = err.encode(defenc)
return out, err
# END helpers
kill_check = threading.Event()
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
else:
communicate = proc.communicate
# Wait for the process to return.
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
stdout_value, stderr_value = communicate()
# Strip trailing "\n".
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# END as_text
if stderr_value:
_logger.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
_logger.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git commit --message=test
E stderr: 'Author identity unknown
E
E *** Please tell me who you are.
E
E Run
E
E git config --global user.email "[email protected]"
E git config --global user.name "Your Name"
E
E to set your account's default identity.
E Omit --global to set the identity only in this repository.
E
E fatal: unable to auto-detect email address (got 'ek@Glub.(none)')'
git/cmd.py:1389: GitCommandError
test/test_submodule.py ⨯✓✓✓✓✓X✓✓✓✓✓✓✓✓s✓✓ 86% ████████▋
test/test_tree.py ✓✓ 86% ████████▋
――――――――――――――――――――――――――――――――――――――――― TestTree.test_tree_modifier_ordering ―――――――――――――――――――――――――――――――――――――――――
self = <test.test_tree.TestTree testMethod=test_tree_modifier_ordering>
def test_tree_modifier_ordering(self):
"""TreeModifier.set_done() sorts files in the same order git does."""
> git_file_names_in_order = self._get_git_ordered_files()
test/test_tree.py:78:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
test/lib/helper.py:99: in wrapper
return func(self, path, *args, **kwargs)
test/test_tree.py:66: in _get_git_ordered_files
subprocess.run(["git", "commit", "-m", "c1"], check=True)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
input = None, capture_output = False, timeout = None, check = True, popenargs = (['git', 'commit', '-m', 'c1'],)
kwargs = {}, process = <Popen: returncode: 128 args: ['git', 'commit', '-m', 'c1']>, stdout = None, stderr = None
retcode = 128
def run(*popenargs,
input=None, capture_output=False, timeout=None, check=False, **kwargs):
"""Run command with arguments and return a CompletedProcess instance.
The returned instance will have attributes args, returncode, stdout and
stderr. By default, stdout and stderr are not captured, and those attributes
will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them,
or pass capture_output=True to capture both.
If check is True and the exit code was non-zero, it raises a
CalledProcessError. The CalledProcessError object will have the return code
in the returncode attribute, and output & stderr attributes if those streams
were captured.
If timeout is given, and the process takes too long, a TimeoutExpired
exception will be raised.
There is an optional argument "input", allowing you to
pass bytes or a string to the subprocess's stdin. If you use this argument
you may not also use the Popen constructor's "stdin" argument, as
it will be used internally.
By default, all communication is in bytes, and therefore any "input" should
be bytes, and the stdout and stderr will be bytes. If in text mode, any
"input" should be a string, and stdout and stderr will be strings decoded
according to locale encoding, or by "encoding" if set. Text mode is
triggered by setting any of text, encoding, errors or universal_newlines.
The other arguments are the same as for the Popen constructor.
"""
if input is not None:
if kwargs.get('stdin') is not None:
raise ValueError('stdin and input arguments may not both be used.')
kwargs['stdin'] = PIPE
if capture_output:
if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None:
raise ValueError('stdout and stderr arguments may not be used '
'with capture_output.')
kwargs['stdout'] = PIPE
kwargs['stderr'] = PIPE
with Popen(*popenargs, **kwargs) as process:
try:
stdout, stderr = process.communicate(input, timeout=timeout)
except TimeoutExpired as exc:
process.kill()
if _mswindows:
# Windows accumulates the output in a single blocking
# read() call run on child threads, with the timeout
# being done in a join() on those threads. communicate()
# _after_ kill() is required to collect that and add it
# to the exception.
exc.stdout, exc.stderr = process.communicate()
else:
# POSIX _communicate already populated the output so
# far into the TimeoutExpired exception.
process.wait()
raise
except: # Including KeyboardInterrupt, communicate handled that.
process.kill()
# We don't call process.wait() as .__exit__ does that for us.
raise
retcode = process.poll()
if check and retcode:
> raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
E subprocess.CalledProcessError: Command '['git', 'commit', '-m', 'c1']' returned non-zero exit status 128.
/usr/lib/python3.12/subprocess.py:571: CalledProcessError
------------------------------------------------- Captured stderr call -------------------------------------------------
Author identity unknown
*** Please tell me who you are.
Run
git config --global user.email "[email protected]"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
fatal: unable to auto-detect email address (got 'ek@Glub.(none)')
test/test_tree.py ⨯ 86% ████████▋
test/test_util.py ✓sssss✓✓ssssssssssssssssssssssssssssss✓✓xxx✓✓✓xxxxxx✓✓x✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 99% █████████▉
✓✓✓✓✓✓ 100% ██████████
=================================================== warnings summary ===================================================
test/test_remote.py:44
/home/ek/repos-msys2/GitPython/test/test_remote.py:44: PytestCollectionWarning: cannot collect test class 'TestRemoteProgress' because it has a __init__ constructor (from: test/test_remote.py)
class TestRemoteProgress(RemoteProgress):
test/test_submodule.py:54
/home/ek/repos-msys2/GitPython/test/test_submodule.py:54: PytestCollectionWarning: cannot collect test class 'TestRootProgress' because it has a __init__ constructor (from: test/test_submodule.py)
class TestRootProgress(RootUpdateProgress):
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================================================= XPASSES ========================================================
____________________________________________ TestSubmodule.test_root_module ____________________________________________
------------------------------------------------- Captured stdout call -------------------------------------------------
513 0 1 DRY-RUN: Cloning url '/home/ek/repos-msys2/GitPython/git/ext/gitdb' to '/tmp/non_bare_test_root_modulekk6sv48m/git/ext/gitdb' in submodule 'gitdb'
514 0 1 DRY-RUN: Done cloning to /tmp/non_bare_test_root_modulekk6sv48m/git/ext/gitdb
8193 0 1 Moving repository of submodule 'gitdb' from /tmp/non_bare_test_root_modulekk6sv48m/git/ext/gitdb to /tmp/non_bare_test_root_modulekk6sv48m/path/prefix/git/ext/gitdb
8194 0 1 Done moving repository of submodule 'gitdb'
1025 0 1 Fetching remote origin of submodule 'gitdb'
1027 0 1 Done fetching remote of submodule 'gitdb'
1025 0 1 DRY-RUN: Fetching remote origin of submodule 'gitdb'
1027 0 1 DRY-RUN: Done fetching remote of submodule 'gitdb'
513 0 1 DRY-RUN: Cloning url '/home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap' to '/tmp/non_bare_test_root_modulekk6sv48m/submrepo' in submodule 'newsubmodule'
514 0 1 DRY-RUN: Done cloning to /tmp/non_bare_test_root_modulekk6sv48m/submrepo
1025 0 1 Fetching remote origin of submodule 'gitdb'
1027 0 1 Done fetching remote of submodule 'gitdb'
513 0 1 Cloning url '/home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap' to '/tmp/non_bare_test_root_modulekk6sv48m/submrepo' in submodule 'newsubmodule'
514 0 1 Done cloning to /tmp/non_bare_test_root_modulekk6sv48m/submrepo
2049 0 1 Updating working tree at submrepo for submodule 'newsubmodule' to revision f31bfa378c8840d38d31e7e11ef2b84f191a491e
2050 0 1 Done updating working tree for submodule 'newsubmodule'
4097 0 1 DRY-RUN: Removing submodule 'gitdb' at /tmp/non_bare_test_root_modulekk6sv48m/path/prefix/git/ext/gitdb
4099 0 1 DRY-RUN: Done removing submodule 'gitdb'
1025 0 1 DRY-RUN: Fetching remote origin of submodule 'newsubmodule'
1027 0 1 DRY-RUN: Done fetching remote of submodule 'newsubmodule'
2049 0 1 DRY-RUN: Updating working tree at submrepo for submodule 'newsubmodule' to revision f31bfa378c8840d38d31e7e11ef2b84f191a491e
2050 0 1 DRY-RUN: Done updating working tree for submodule 'newsubmodule'
4097 0 1 Removing submodule 'gitdb' at /tmp/non_bare_test_root_modulekk6sv48m/path/prefix/git/ext/gitdb
4099 0 1 Done removing submodule 'gitdb'
1025 0 1 Fetching remote origin of submodule 'newsubmodule'
1027 0 1 Done fetching remote of submodule 'newsubmodule'
2049 0 1 Updating working tree at submrepo for submodule 'newsubmodule' to revision f31bfa378c8840d38d31e7e11ef2b84f191a491e
2050 0 1 Done updating working tree for submodule 'newsubmodule'
4097 0 1 Removing submodule 'gitdb' at /tmp/non_bare_test_root_modulekk6sv48m/path/prefix/git/ext/gitdb
4099 0 1 Done removing submodule 'gitdb'
1025 0 1 Fetching remote origin of submodule 'newsubmodule'
1027 0 1 Done fetching remote of submodule 'newsubmodule'
2049 0 1 Updating working tree at submrepo for submodule 'newsubmodule' to revision f31bfa378c8840d38d31e7e11ef2b84f191a491e
2050 0 1 Done updating working tree for submodule 'newsubmodule'
32769 0 1 DRY-RUN: Changing url of submodule 'newsubmodule' from /home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap to /home/ek/repos-msys2/GitPython/git/ext/gitdb
32770 0 1 DRY-RUN: Done adjusting url of submodule 'newsubmodule'
1025 0 1 DRY-RUN: Fetching remote origin of submodule 'newsubmodule'
1027 0 1 DRY-RUN: Done fetching remote of submodule 'newsubmodule'
32769 0 1 Changing url of submodule 'newsubmodule' from /home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap to /home/ek/repos-msys2/GitPython/git/ext/gitdb
5 1.0 2518.0
4 26.0 2518.0
4 51.0 2518.0
4 76.0 2518.0
4 101.0 2518.0
4 126.0 2518.0
4 152.0 2518.0
4 177.0 2518.0
4 202.0 2518.0
4 227.0 2518.0
4 252.0 2518.0
4 277.0 2518.0
4 303.0 2518.0
4 328.0 2518.0
4 353.0 2518.0
4 378.0 2518.0
4 403.0 2518.0
4 429.0 2518.0
4 454.0 2518.0
4 479.0 2518.0
4 504.0 2518.0
4 529.0 2518.0
4 554.0 2518.0
4 580.0 2518.0
4 605.0 2518.0
4 630.0 2518.0
4 655.0 2518.0
4 680.0 2518.0
4 706.0 2518.0
4 731.0 2518.0
4 756.0 2518.0
4 781.0 2518.0
4 806.0 2518.0
4 831.0 2518.0
4 857.0 2518.0
4 882.0 2518.0
4 907.0 2518.0
4 932.0 2518.0
4 957.0 2518.0
4 983.0 2518.0
4 1008.0 2518.0
4 1033.0 2518.0
4 1058.0 2518.0
4 1083.0 2518.0
4 1108.0 2518.0
4 1134.0 2518.0
4 1159.0 2518.0
4 1184.0 2518.0
4 1209.0 2518.0
4 1234.0 2518.0
4 1259.0 2518.0
4 1285.0 2518.0
4 1310.0 2518.0
4 1335.0 2518.0
4 1360.0 2518.0
4 1385.0 2518.0
4 1411.0 2518.0
4 1436.0 2518.0
4 1461.0 2518.0
4 1486.0 2518.0
4 1511.0 2518.0
4 1536.0 2518.0
4 1562.0 2518.0
4 1587.0 2518.0
4 1612.0 2518.0
4 1637.0 2518.0
4 1662.0 2518.0
4 1688.0 2518.0
4 1713.0 2518.0
4 1738.0 2518.0
4 1763.0 2518.0
4 1788.0 2518.0
4 1813.0 2518.0
4 1839.0 2518.0
4 1864.0 2518.0
4 1889.0 2518.0
4 1914.0 2518.0
4 1939.0 2518.0
4 1965.0 2518.0
4 1990.0 2518.0
4 2015.0 2518.0
4 2040.0 2518.0
4 2065.0 2518.0
4 2090.0 2518.0
4 2116.0 2518.0
4 2141.0 2518.0
4 2166.0 2518.0
4 2191.0 2518.0
4 2216.0 2518.0
4 2242.0 2518.0
4 2267.0 2518.0
4 2292.0 2518.0
4 2317.0 2518.0
4 2342.0 2518.0
4 2367.0 2518.0
4 2393.0 2518.0
4 2418.0 2518.0
4 2443.0 2518.0
4 2468.0 2518.0
4 2493.0 2518.0
4 2518.0 2518.0
6 2518.0 2518.0
9 1.0 904.0
8 10.0 904.0
8 19.0 904.0
8 28.0 904.0
8 37.0 904.0
8 46.0 904.0
8 55.0 904.0
8 64.0 904.0
8 73.0 904.0
8 82.0 904.0
8 91.0 904.0
8 100.0 904.0
8 109.0 904.0
8 118.0 904.0
8 127.0 904.0
8 136.0 904.0
8 145.0 904.0
8 154.0 904.0
8 163.0 904.0
8 172.0 904.0
8 181.0 904.0
8 190.0 904.0
8 199.0 904.0
8 208.0 904.0
8 217.0 904.0
8 226.0 904.0
8 236.0 904.0
8 245.0 904.0
8 254.0 904.0
8 263.0 904.0
8 272.0 904.0
8 281.0 904.0
8 290.0 904.0
8 299.0 904.0
8 308.0 904.0
8 317.0 904.0
8 326.0 904.0
8 335.0 904.0
8 344.0 904.0
8 353.0 904.0
8 362.0 904.0
8 371.0 904.0
8 380.0 904.0
8 389.0 904.0
8 398.0 904.0
8 407.0 904.0
8 416.0 904.0
8 425.0 904.0
8 434.0 904.0
8 443.0 904.0
8 452.0 904.0
8 462.0 904.0
8 471.0 904.0
8 480.0 904.0
8 489.0 904.0
8 498.0 904.0
8 507.0 904.0
8 516.0 904.0
8 525.0 904.0
8 534.0 904.0
8 543.0 904.0
8 552.0 904.0
8 561.0 904.0
8 570.0 904.0
8 579.0 904.0
8 588.0 904.0
8 597.0 904.0
8 606.0 904.0
8 615.0 904.0
8 624.0 904.0
8 633.0 904.0
8 642.0 904.0
8 651.0 904.0
8 660.0 904.0
8 669.0 904.0
8 678.0 904.0
8 688.0 904.0
8 697.0 904.0
8 706.0 904.0
8 715.0 904.0
8 724.0 904.0
8 733.0 904.0
8 742.0 904.0
8 751.0 904.0
8 760.0 904.0
8 769.0 904.0
8 778.0 904.0
8 787.0 904.0
8 796.0 904.0
8 805.0 904.0
8 814.0 904.0
8 823.0 904.0
8 832.0 904.0
8 841.0 904.0
8 850.0 904.0
8 859.0 904.0
8 868.0 904.0
8 877.0 904.0
8 886.0 904.0
8 895.0 904.0
8 904.0 904.0
10 904.0 904.0
33 1.0 2518.0
32 26.0 2518.0
32 51.0 2518.0
32 76.0 2518.0
32 101.0 2518.0
32 126.0 2518.0
32 152.0 2518.0
32 177.0 2518.0
32 202.0 2518.0
32 227.0 2518.0
32 252.0 2518.0
32 277.0 2518.0
32 303.0 2518.0
32 328.0 2518.0
32 353.0 2518.0
32 378.0 2518.0
32 403.0 2518.0
32 429.0 2518.0
32 454.0 2518.0
32 479.0 2518.0
32 504.0 2518.0
32 529.0 2518.0
32 554.0 2518.0
32 580.0 2518.0
32 605.0 2518.0
32 630.0 2518.0
32 655.0 2518.0
32 680.0 2518.0
32 706.0 2518.0
32 731.0 2518.0
32 756.0 2518.0
32 781.0 2518.0
32 806.0 2518.0
32 831.0 2518.0
32 857.0 2518.0
32 882.0 2518.0
32 907.0 2518.0
32 932.0 2518.0
32 957.0 2518.0
32 983.0 2518.0
32 1008.0 2518.0
32 1033.0 2518.0
32 1058.0 2518.0
32 1083.0 2518.0
32 1108.0 2518.0
32 1134.0 2518.0
32 1159.0 2518.0
32 1184.0 2518.0
32 1209.0 2518.0
32 1234.0 2518.0
32 1259.0 2518.0
32 1285.0 2518.0
32 1310.0 2518.0
32 1335.0 2518.0
32 1360.0 2518.0
32 1385.0 2518.0
32 1411.0 2518.0
32 1436.0 2518.0
32 1461.0 2518.0
32 1486.0 2518.0
32 1511.0 2518.0
32 1536.0 2518.0
32 1562.0 2518.0
32 1587.0 2518.0
32 1612.0 2518.0
32 1637.0 2518.0
32 1662.0 2518.0
32 1688.0 2518.0
32 1713.0 2518.0
32 1738.0 2518.0
32 1763.0 2518.0
32 1788.0 2518.0
32 1813.0 2518.0
32 1839.0 2518.0
32 1864.0 2518.0
32 1889.0 2518.0
32 1914.0 2518.0
32 1939.0 2518.0
32 1965.0 2518.0
32 1990.0 2518.0
32 2015.0 2518.0
32 2040.0 2518.0
32 2065.0 2518.0
32 2090.0 2518.0
32 2116.0 2518.0
32 2141.0 2518.0
32 2166.0 2518.0
32 2191.0 2518.0
32 2216.0 2518.0
32 2242.0 2518.0
32 2267.0 2518.0
32 2292.0 2518.0
32 2317.0 2518.0
32 2342.0 2518.0
32 2367.0 2518.0
32 2393.0 2518.0
32 2418.0 2518.0
32 2443.0 2518.0
32 2468.0 2518.0
32 2493.0 2518.0
32 2518.0 2518.0
34 2518.0 2518.0 1.34 MiB | 20.86 MiB/s
65 0.0 1534.0
64 16.0 1534.0
64 36.0 1534.0
64 47.0 1534.0
64 62.0 1534.0
64 77.0 1534.0
64 93.0 1534.0
64 109.0 1534.0
64 124.0 1534.0
64 139.0 1534.0
64 154.0 1534.0
64 169.0 1534.0
64 185.0 1534.0
64 200.0 1534.0
64 215.0 1534.0
64 231.0 1534.0
64 246.0 1534.0
64 262.0 1534.0
64 277.0 1534.0
64 292.0 1534.0
64 309.0 1534.0
64 323.0 1534.0
64 338.0 1534.0
64 354.0 1534.0
64 369.0 1534.0
64 384.0 1534.0
64 399.0 1534.0
64 415.0 1534.0
64 430.0 1534.0
64 445.0 1534.0
64 461.0 1534.0
64 476.0 1534.0
64 491.0 1534.0
64 507.0 1534.0
64 522.0 1534.0
64 537.0 1534.0
64 555.0 1534.0
64 569.0 1534.0
64 585.0 1534.0
64 601.0 1534.0
64 615.0 1534.0
64 629.0 1534.0
64 645.0 1534.0
64 661.0 1534.0
64 676.0 1534.0
64 692.0 1534.0
64 707.0 1534.0
64 722.0 1534.0
64 737.0 1534.0
64 752.0 1534.0
64 767.0 1534.0
64 783.0 1534.0
64 803.0 1534.0
64 814.0 1534.0
64 829.0 1534.0
64 844.0 1534.0
64 861.0 1534.0
64 875.0 1534.0
64 890.0 1534.0
64 906.0 1534.0
64 921.0 1534.0
64 936.0 1534.0
64 952.0 1534.0
64 967.0 1534.0
64 985.0 1534.0
64 999.0 1534.0
64 1013.0 1534.0
64 1029.0 1534.0
64 1044.0 1534.0
64 1062.0 1534.0
64 1074.0 1534.0
64 1092.0 1534.0
64 1105.0 1534.0
64 1121.0 1534.0
64 1136.0 1534.0
64 1151.0 1534.0
64 1166.0 1534.0
64 1184.0 1534.0
64 1197.0 1534.0
64 1213.0 1534.0
64 1229.0 1534.0
64 1243.0 1534.0
64 1259.0 1534.0
64 1274.0 1534.0
64 1289.0 1534.0
64 1307.0 1534.0
64 1320.0 1534.0
64 1335.0 1534.0
64 1350.0 1534.0
64 1368.0 1534.0
64 1382.0 1534.0
64 1397.0 1534.0
64 1412.0 1534.0
64 1432.0 1534.0
64 1443.0 1534.0
64 1460.0 1534.0
64 1473.0 1534.0
64 1488.0 1534.0
64 1504.0 1534.0
64 1519.0 1534.0
64 1534.0 1534.0
66 1534.0 1534.0
32770 0 1 Done adjusting url of submodule 'newsubmodule'
1025 0 1 Fetching remote origin of submodule 'newsubmodule'
1027 0 1 Done fetching remote of submodule 'newsubmodule'
2049 0 1 Updating working tree at submrepo for submodule 'newsubmodule' to revision 775cfe8299ea5474f605935469359a9d1cdb49dc
2050 0 1 Done updating working tree for submodule 'newsubmodule'
16385 0 1 DRY-RUN: Changing branch of submodule 'newsubmodule' from refs/heads/some_virtual_branch to refs/heads/master
16386 0 1 DRY-RUN: Done changing branch of submodule 'newsubmodule'
1025 0 1 DRY-RUN: Fetching remote origin of submodule 'newsubmodule'
1027 0 1 DRY-RUN: Done fetching remote of submodule 'newsubmodule'
16385 0 1 Changing branch of submodule 'newsubmodule' from refs/heads/some_virtual_branch to refs/heads/master
16386 0 1 Done changing branch of submodule 'newsubmodule'
1025 0 1 Fetching remote origin of submodule 'newsubmodule'
1027 0 1 Done fetching remote of submodule 'newsubmodule'
16385 0 1 DRY-RUN: Changing branch of submodule 'newsubmodule' from refs/heads/some_virtual_branch to refs/heads/master
16386 0 1 DRY-RUN: Done changing branch of submodule 'newsubmodule'
1025 0 1 DRY-RUN: Fetching remote origin of submodule 'newsubmodule'
1027 0 1 DRY-RUN: Done fetching remote of submodule 'newsubmodule'
513 0 1 DRY-RUN: Cloning url '/home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap' to '/tmp/non_bare_test_root_modulekk6sv48m/submrepo/gitdb/ext/smmap' in submodule 'smmap'
514 0 1 DRY-RUN: Done cloning to /tmp/non_bare_test_root_modulekk6sv48m/submrepo/gitdb/ext/smmap
16385 0 1 Changing branch of submodule 'newsubmodule' from refs/heads/some_virtual_branch to refs/heads/master
16386 0 1 Done changing branch of submodule 'newsubmodule'
1025 0 1 Fetching remote origin of submodule 'newsubmodule'
1027 0 1 Done fetching remote of submodule 'newsubmodule'
513 0 1 Cloning url '/home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap' to '/tmp/non_bare_test_root_modulekk6sv48m/submrepo/gitdb/ext/smmap' in submodule 'smmap'
514 0 1 Done cloning to /tmp/non_bare_test_root_modulekk6sv48m/submrepo/gitdb/ext/smmap
2049 0 1 Updating working tree at gitdb/ext/smmap for submodule 'smmap' to revision f31bfa378c8840d38d31e7e11ef2b84f191a491e
2050 0 1 Done updating working tree for submodule 'smmap'
-------------------------------------------------- Captured log call ---------------------------------------------------
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.objects.submodule.root:root.py:345 Current sha f31bfa378c8840d38d31e7e11ef2b84f191a491e was not contained in the tracking branch at the new remote, setting it the the remote's tracking branch
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
---------- coverage: platform cygwin, python 3.12.8-final-0 ----------
Name Stmts Miss Cover
-------------------------------------------------------
git/__init__.py 51 6 88%
git/cmd.py 601 86 86%
git/compat.py 62 15 76%
git/config.py 441 47 89%
git/db.py 26 1 96%
git/diff.py 290 9 97%
git/exc.py 66 3 95%
git/index/__init__.py 3 0 100%
git/index/base.py 500 93 81%
git/index/fun.py 188 14 93%
git/index/typ.py 76 3 96%
git/index/util.py 52 2 96%
git/objects/__init__.py 7 0 100%
git/objects/base.py 91 10 89%
git/objects/blob.py 20 1 95%
git/objects/commit.py 319 28 91%
git/objects/fun.py 99 3 97%
git/objects/submodule/__init__.py 3 0 100%
git/objects/submodule/base.py 578 38 93%
git/objects/submodule/root.py 136 12 91%
git/objects/submodule/util.py 50 8 84%
git/objects/tag.py 48 12 75%
git/objects/tree.py 150 42 72%
git/objects/util.py 228 23 90%
git/refs/__init__.py 7 0 100%
git/refs/head.py 98 7 93%
git/refs/log.py 150 10 93%
git/refs/reference.py 55 3 95%
git/refs/remote.py 32 12 62%
git/refs/symbolic.py 369 81 78%
git/refs/tag.py 47 5 89%
git/remote.py 461 87 81%
git/repo/__init__.py 2 0 100%
git/repo/base.py 587 39 93%
git/repo/fun.py 204 19 91%
git/types.py 65 7 89%
git/util.py 559 83 85%
-------------------------------------------------------
TOTAL 6721 809 88%
=============================================== short test summary info ================================================
SKIPPED [1] test/test_config.py:91: Known failure -- included values are not in effect right away
SKIPPED [1] test/test_git.py:248: The regression only affected Windows, and this test logic is OS-specific.
SKIPPED [1] test/test_repo.py:261: The referenced repository was removed, and one needs to set up a new
password controlled repo under the org's control.
SKIPPED [1] test/test_submodule.py:1075: Specifically for Windows.
SKIPPED [1] test/test_util.py:88: Cygwin can't set the permissions that make the test meaningful.
SKIPPED [1] test/test_util.py:111: Cygwin can't set the permissions that make the test meaningful.
SKIPPED [1] test/test_util.py:152: PermissionError is only ever wrapped on Windows
SKIPPED [2] test/test_util.py:164: Cygwin can't set the permissions that make the test meaningful.
SKIPPED [30] test/test_util.py:217: These environment variables are only used on Windows.
xfail test/test_submodule.py::TestSubmodule::test_depth - for some unknown reason the assertion fails, even though it in fact is working in more common setup
xfail test/test_util.py::TestCygpath::test_cygpath_ok[C:\\Users-/cygdrive/c/Users] - Returns: '/proc/cygdrive/c/Users'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[C:\\d/e-/cygdrive/c/d/e] - Returns: '/proc/cygdrive/c/d/e'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[C:\\-/cygdrive/c/] - Returns: '/proc/cygdrive/c/'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[\\\\server\\BAR/-//server/BAR/] - Returns: '//server/BAR'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[D:/Apps-/cygdrive/d/Apps] - Returns: '/proc/cygdrive/d/Apps'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[D:/Apps\\fOO-/cygdrive/d/Apps/fOO] - Returns: '/proc/cygdrive/d/Apps/fOO'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[D:\\Apps/123-/cygdrive/d/Apps/123] - Returns: '/proc/cygdrive/d/Apps/123'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[\\\\?\\a:\\com-/cygdrive/a/com] - Returns: '/proc/cygdrive/a/com'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[\\\\?\\a:/com-/cygdrive/a/com] - Returns: '/proc/cygdrive/a/com'
xfail test/test_util.py::TestCygpath::test_cygpath_norm_ok[.\\bar-bar] - Returns: './bar'
XPASS test/test_docs.py::Tutorials::test_submodules - Cygwin GitPython can't find SHA for submodule
XPASS test/test_repo.py::TestRepo::test_submodules - Cygwin GitPython can't find submodule SHA
XPASS test/test_submodule.py::TestSubmodule::test_root_module - Cygwin GitPython can't find submodule SHA
FAILED test/test_diff.py::TestDiff::test_diff_with_staged_file - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_diff.py::TestDiff::test_rename_override - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_docs.py::Tutorials::test_init_repo_object - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_docs.py::Tutorials::test_references_and_objects - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_index.py::TestIndex::test_index_mutation - FileNotFoundError: [Errno 2] No such file or directory: '/etc/nonexisting' -> '/tmp/non_bare_test_index_mutationn7n...
FAILED test/test_refs.py::TestRefs::test_head_reset - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_refs.py::TestRefs::test_tag_message - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_remote.py::TestRemote::test_base - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_repo.py::TestRepo::test_do_not_strip_newline_in_stdout - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_repo.py::TestRepo::test_rebasing - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_submodule.py::TestSubmodule::test_git_submodules_and_add_sm_with_new_commit - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_submodule.py::TestSubmodule::test_ignore_non_submodule_file - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_tree.py::TestTree::test_tree_modifier_ordering - subprocess.CalledProcessError: Command '['git', 'commit', '-m', 'c1']' returned non-zero exit status 128.
Results (1060.93s (0:17:40)):
602 passed
3 xpassed
13 failed
- test/test_diff.py:51 TestDiff.test_diff_with_staged_file
- test/test_diff.py:423 TestDiff.test_rename_override
- test/test_docs.py:28 Tutorials.test_init_repo_object
- test/test_docs.py:209 Tutorials.test_references_and_objects
- test/test_index.py:558 TestIndex.test_index_mutation
- test/test_refs.py:219 TestRefs.test_head_reset
- test/test_refs.py:596 TestRefs.test_tag_message
- test/test_remote.py:434 TestRemote.test_base
- test/test_repo.py:1378 TestRepo.test_do_not_strip_newline_in_stdout
- test/test_repo.py:1353 TestRepo.test_rebasing
- test/test_submodule.py:775 TestSubmodule.test_git_submodules_and_add_sm_with_new_commit
- test/test_submodule.py:938 TestSubmodule.test_ignore_non_submodule_file
- test/test_tree.py:76 TestTree.test_tree_modifier_ordering
11 xfailed
39 skipped
(.venv)
ek@Glub MSYS ~/repos-msys2/GitPython
$ git config --global user.name 'Eliah Kagan'
(.venv)
ek@Glub MSYS ~/repos-msys2/GitPython
$ git config --global user.email '[email protected]'
(.venv)
ek@Glub MSYS ~/repos-msys2/GitPython
$ pytest
Test session starts (platform: cygwin, Python 3.12.8, pytest 8.3.4, pytest-sugar 1.0.0)
rootdir: /home/ek/repos-msys2/GitPython
configfile: pyproject.toml
testpaths: test
plugins: cov-6.0.0, instafail-0.5.0, mock-3.14.0, sugar-1.0.0
collected 668 items
test/deprecation/test_basic.py ✓✓✓✓✓✓✓✓✓✓✓✓ 2% ▎
test/deprecation/test_cmd_git.py ✓✓✓✓✓✓✓✓✓✓✓✓✓ 4% ▍
test/deprecation/test_compat.py ✓✓✓ 4% ▌
test/deprecation/test_toplevel.py ✓✓✓✓✓✓✓✓✓✓ 6% ▋
test/deprecation/test_types.py ✓✓✓ 6% ▋
test/performance/test_commit.py ✓✓✓✓ 7% ▋
test/performance/test_odb.py ✓ 7% ▊
test/performance/test_streams.py ✓ 7% ▊
test/test_actor.py ✓✓✓✓ 8% ▊
test/test_base.py ✓✓✓✓✓✓✓ 9% ▉
test/test_blob.py ✓✓✓ 9% ▉
test/test_blob_filter.py ✓✓✓✓ 10% █
test/test_clone.py ✓ 10% █
test/test_commit.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 14% █▍
test/test_config.py ✓✓✓✓✓✓✓✓✓✓s✓✓✓✓✓✓✓✓✓ 17% █▋
test/test_db.py ✓ 17% █▊
test/test_diff.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 20% ██
test/test_docs.py ✓✓✓X 21% ██▏
test/test_exc.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 34% ███▍
✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 44% ████▌
test/test_fun.py ✓✓✓✓✓✓✓ 45% ████▋
test/test_git.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓s✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 58% █████▉
✓✓✓ 59% █████▉
test/test_imports.py ✓✓✓ 59% █████▉
test/test_index.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 62% ██████▎
―――――――――――――――――――――――――――――――――――――――――――― TestIndex.test_index_mutation ―――――――――――――――――――――――――――――――――――――――――――――
self = <test.test_index.TestIndex testMethod=test_index_mutation>
rw_repo = <git.repo.base.Repo '/tmp/non_bare_test_index_mutationonpsnzb4/.git'>
@pytest.mark.xfail(
sys.platform == "win32" and Git().config("core.symlinks") == "true",
reason="Assumes symlinks are not created on Windows and opens a symlink to a nonexistent target.",
raises=FileNotFoundError,
)
@with_rw_repo("0.1.6")
def test_index_mutation(self, rw_repo):
index = rw_repo.index
num_entries = len(index.entries)
cur_head = rw_repo.head
uname = "Thomas Müller"
umail = "[email protected]"
with rw_repo.config_writer() as writer:
writer.set_value("user", "name", uname)
writer.set_value("user", "email", umail)
self.assertEqual(writer.get_value("user", "name"), uname)
# Remove all of the files, provide a wild mix of paths, BaseIndexEntries,
# IndexEntries.
def mixed_iterator():
count = 0
for entry in index.entries.values():
type_id = count % 5
if type_id == 0: # path (str)
yield entry.path
elif type_id == 1: # path (PathLike)
yield Path(entry.path)
elif type_id == 2: # blob
yield Blob(rw_repo, entry.binsha, entry.mode, entry.path)
elif type_id == 3: # BaseIndexEntry
yield BaseIndexEntry(entry[:4])
elif type_id == 4: # IndexEntry
yield entry
else:
raise AssertionError("Invalid Type")
count += 1
# END for each entry
# END mixed iterator
deleted_files = index.remove(mixed_iterator(), working_tree=False)
assert deleted_files
self.assertEqual(self._count_existing(rw_repo, deleted_files), len(deleted_files))
self.assertEqual(len(index.entries), 0)
# Reset the index to undo our changes.
index.reset()
self.assertEqual(len(index.entries), num_entries)
# Remove with working copy.
deleted_files = index.remove(mixed_iterator(), working_tree=True)
assert deleted_files
self.assertEqual(self._count_existing(rw_repo, deleted_files), 0)
# Reset everything.
index.reset(working_tree=True)
self.assertEqual(self._count_existing(rw_repo, deleted_files), len(deleted_files))
# Invalid type.
self.assertRaises(TypeError, index.remove, [1])
# Absolute path.
deleted_files = index.remove([osp.join(rw_repo.working_tree_dir, "lib")], r=True)
assert len(deleted_files) > 1
self.assertRaises(ValueError, index.remove, ["/doesnt/exists"])
# TEST COMMITTING
# Commit changed index.
cur_commit = cur_head.commit
commit_message = "commit default head by Frèderic Çaufl€"
new_commit = index.commit(commit_message, head=False)
assert cur_commit != new_commit
self.assertEqual(new_commit.author.name, uname)
self.assertEqual(new_commit.author.email, umail)
self.assertEqual(new_commit.committer.name, uname)
self.assertEqual(new_commit.committer.email, umail)
self.assertEqual(new_commit.message, commit_message)
self.assertEqual(new_commit.parents[0], cur_commit)
self.assertEqual(len(new_commit.parents), 1)
self.assertEqual(cur_head.commit, cur_commit)
# Commit with other actor.
cur_commit = cur_head.commit
my_author = Actor("Frèderic Çaufl€", "[email protected]")
my_committer = Actor("Committing Frèderic Çaufl€", "[email protected]")
commit_actor = index.commit(commit_message, author=my_author, committer=my_committer)
assert cur_commit != commit_actor
self.assertEqual(commit_actor.author.name, "Frèderic Çaufl€")
self.assertEqual(commit_actor.author.email, "[email protected]")
self.assertEqual(commit_actor.committer.name, "Committing Frèderic Çaufl€")
self.assertEqual(commit_actor.committer.email, "[email protected]")
self.assertEqual(commit_actor.message, commit_message)
self.assertEqual(commit_actor.parents[0], cur_commit)
self.assertEqual(len(new_commit.parents), 1)
self.assertEqual(cur_head.commit, commit_actor)
self.assertEqual(cur_head.log()[-1].actor, my_committer)
# Commit with author_date and commit_date.
cur_commit = cur_head.commit
commit_message = "commit with dates by Avinash Sajjanshetty"
new_commit = index.commit(
commit_message,
author_date="2006-04-07T22:13:13",
commit_date="2005-04-07T22:13:13",
)
assert cur_commit != new_commit
print(new_commit.authored_date, new_commit.committed_date)
self.assertEqual(new_commit.message, commit_message)
self.assertEqual(new_commit.authored_date, 1144447993)
self.assertEqual(new_commit.committed_date, 1112911993)
# Same index, no parents.
commit_message = "index without parents"
commit_no_parents = index.commit(commit_message, parent_commits=[], head=True)
self.assertEqual(commit_no_parents.message, commit_message)
self.assertEqual(len(commit_no_parents.parents), 0)
self.assertEqual(cur_head.commit, commit_no_parents)
# same index, multiple parents.
commit_message = "Index with multiple parents\n commit with another line"
commit_multi_parent = index.commit(commit_message, parent_commits=(commit_no_parents, new_commit))
self.assertEqual(commit_multi_parent.message, commit_message)
self.assertEqual(len(commit_multi_parent.parents), 2)
self.assertEqual(commit_multi_parent.parents[0], commit_no_parents)
self.assertEqual(commit_multi_parent.parents[1], new_commit)
self.assertEqual(cur_head.commit, commit_multi_parent)
# Re-add all files in lib.
# Get the lib folder back on disk, but get an index without it.
index.reset(new_commit.parents[0], working_tree=True).reset(new_commit, working_tree=False)
lib_file_path = osp.join("lib", "git", "__init__.py")
assert (lib_file_path, 0) not in index.entries
assert osp.isfile(osp.join(rw_repo.working_tree_dir, lib_file_path))
# Directory.
entries = index.add(["lib"], fprogress=self._fprogress_add)
self._assert_entries(entries)
self._assert_fprogress(entries)
assert len(entries) > 1
# Glob.
entries = index.reset(new_commit).add([osp.join("lib", "git", "*.py")], fprogress=self._fprogress_add)
self._assert_entries(entries)
self._assert_fprogress(entries)
self.assertEqual(len(entries), 14)
# Same file.
entries = index.reset(new_commit).add(
[osp.join(rw_repo.working_tree_dir, "lib", "git", "head.py")] * 2,
fprogress=self._fprogress_add,
)
self._assert_entries(entries)
self.assertEqual(entries[0].mode & 0o644, 0o644)
# Would fail, test is too primitive to handle this case.
# self._assert_fprogress(entries)
self._reset_progress()
self.assertEqual(len(entries), 2)
# Missing path.
self.assertRaises(OSError, index.reset(new_commit).add, ["doesnt/exist/must/raise"])
# Blob from older revision overrides current index revision.
old_blob = new_commit.parents[0].tree.blobs[0]
entries = index.reset(new_commit).add([old_blob], fprogress=self._fprogress_add)
self._assert_entries(entries)
self._assert_fprogress(entries)
self.assertEqual(index.entries[(old_blob.path, 0)].hexsha, old_blob.hexsha)
self.assertEqual(len(entries), 1)
# Mode 0 not allowed.
null_hex_sha = Diff.NULL_HEX_SHA
null_bin_sha = b"\0" * 20
self.assertRaises(
ValueError,
index.reset(new_commit).add,
[BaseIndexEntry((0, null_bin_sha, 0, "doesntmatter"))],
)
# Add new file.
new_file_relapath = "my_new_file"
self._make_file(new_file_relapath, "hello world", rw_repo)
entries = index.reset(new_commit).add(
[BaseIndexEntry((0o10644, null_bin_sha, 0, new_file_relapath))],
fprogress=self._fprogress_add,
)
self._assert_entries(entries)
self._assert_fprogress(entries)
self.assertEqual(len(entries), 1)
self.assertNotEqual(entries[0].hexsha, null_hex_sha)
# Add symlink.
if sys.platform != "win32":
for target in ("/etc/nonexisting", "/etc/passwd", "/etc"):
basename = "my_real_symlink"
link_file = osp.join(rw_repo.working_tree_dir, basename)
> os.symlink(target, link_file)
E FileNotFoundError: [Errno 2] No such file or directory: '/etc/nonexisting' -> '/tmp/non_bare_test_index_mutationonpsnzb4/my_real_symlink'
test/test_index.py:757: FileNotFoundError
------------------------------------------------- Captured stdout call -------------------------------------------------
1144447993 1112911993
test/test_index.py ⨯✓✓✓✓✓✓✓ 63% ██████▍
test/test_installation.py ✓ 63% ██████▍
test/test_quick_doc.py ✓✓ 63% ██████▍
test/test_reflog.py ✓✓ 64% ██████▍
test/test_refs.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 66% ██████▋
test/test_remote.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 70% ███████
test/test_repo.py ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓s✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓X✓✓✓✓✓✓ 81% ████████▎
test/test_stats.py ✓ 82% ████████▎
test/test_submodule.py ✓✓✓✓✓✓x✓✓✓✓✓✓✓✓✓X✓✓✓✓✓✓✓✓s✓✓ 86% ████████▋
test/test_tree.py ✓✓✓ 86% ████████▋
test/test_util.py ✓sssss✓✓ssssssssssssssssssssssssssssss✓✓xxx✓✓✓xxxxxx✓✓x✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓ 99% █████████▉
✓✓✓✓✓✓ 100% ██████████
=================================================== warnings summary ===================================================
test/test_remote.py:44
/home/ek/repos-msys2/GitPython/test/test_remote.py:44: PytestCollectionWarning: cannot collect test class 'TestRemoteProgress' because it has a __init__ constructor (from: test/test_remote.py)
class TestRemoteProgress(RemoteProgress):
test/test_submodule.py:54
/home/ek/repos-msys2/GitPython/test/test_submodule.py:54: PytestCollectionWarning: cannot collect test class 'TestRootProgress' because it has a __init__ constructor (from: test/test_submodule.py)
class TestRootProgress(RootUpdateProgress):
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================================================= XPASSES ========================================================
____________________________________________ TestSubmodule.test_root_module ____________________________________________
------------------------------------------------- Captured stdout call -------------------------------------------------
513 0 1 DRY-RUN: Cloning url '/home/ek/repos-msys2/GitPython/git/ext/gitdb' to '/tmp/non_bare_test_root_modulert2m1s_2/git/ext/gitdb' in submodule 'gitdb'
514 0 1 DRY-RUN: Done cloning to /tmp/non_bare_test_root_modulert2m1s_2/git/ext/gitdb
8193 0 1 Moving repository of submodule 'gitdb' from /tmp/non_bare_test_root_modulert2m1s_2/git/ext/gitdb to /tmp/non_bare_test_root_modulert2m1s_2/path/prefix/git/ext/gitdb
8194 0 1 Done moving repository of submodule 'gitdb'
1025 0 1 Fetching remote origin of submodule 'gitdb'
1027 0 1 Done fetching remote of submodule 'gitdb'
1025 0 1 DRY-RUN: Fetching remote origin of submodule 'gitdb'
1027 0 1 DRY-RUN: Done fetching remote of submodule 'gitdb'
513 0 1 DRY-RUN: Cloning url '/home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap' to '/tmp/non_bare_test_root_modulert2m1s_2/submrepo' in submodule 'newsubmodule'
514 0 1 DRY-RUN: Done cloning to /tmp/non_bare_test_root_modulert2m1s_2/submrepo
1025 0 1 Fetching remote origin of submodule 'gitdb'
1027 0 1 Done fetching remote of submodule 'gitdb'
513 0 1 Cloning url '/home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap' to '/tmp/non_bare_test_root_modulert2m1s_2/submrepo' in submodule 'newsubmodule'
514 0 1 Done cloning to /tmp/non_bare_test_root_modulert2m1s_2/submrepo
2049 0 1 Updating working tree at submrepo for submodule 'newsubmodule' to revision f31bfa378c8840d38d31e7e11ef2b84f191a491e
2050 0 1 Done updating working tree for submodule 'newsubmodule'
4097 0 1 DRY-RUN: Removing submodule 'gitdb' at /tmp/non_bare_test_root_modulert2m1s_2/path/prefix/git/ext/gitdb
4099 0 1 DRY-RUN: Done removing submodule 'gitdb'
1025 0 1 DRY-RUN: Fetching remote origin of submodule 'newsubmodule'
1027 0 1 DRY-RUN: Done fetching remote of submodule 'newsubmodule'
2049 0 1 DRY-RUN: Updating working tree at submrepo for submodule 'newsubmodule' to revision f31bfa378c8840d38d31e7e11ef2b84f191a491e
2050 0 1 DRY-RUN: Done updating working tree for submodule 'newsubmodule'
4097 0 1 Removing submodule 'gitdb' at /tmp/non_bare_test_root_modulert2m1s_2/path/prefix/git/ext/gitdb
4099 0 1 Done removing submodule 'gitdb'
1025 0 1 Fetching remote origin of submodule 'newsubmodule'
1027 0 1 Done fetching remote of submodule 'newsubmodule'
2049 0 1 Updating working tree at submrepo for submodule 'newsubmodule' to revision f31bfa378c8840d38d31e7e11ef2b84f191a491e
2050 0 1 Done updating working tree for submodule 'newsubmodule'
4097 0 1 Removing submodule 'gitdb' at /tmp/non_bare_test_root_modulert2m1s_2/path/prefix/git/ext/gitdb
4099 0 1 Done removing submodule 'gitdb'
1025 0 1 Fetching remote origin of submodule 'newsubmodule'
1027 0 1 Done fetching remote of submodule 'newsubmodule'
2049 0 1 Updating working tree at submrepo for submodule 'newsubmodule' to revision f31bfa378c8840d38d31e7e11ef2b84f191a491e
2050 0 1 Done updating working tree for submodule 'newsubmodule'
32769 0 1 DRY-RUN: Changing url of submodule 'newsubmodule' from /home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap to /home/ek/repos-msys2/GitPython/git/ext/gitdb
32770 0 1 DRY-RUN: Done adjusting url of submodule 'newsubmodule'
1025 0 1 DRY-RUN: Fetching remote origin of submodule 'newsubmodule'
1027 0 1 DRY-RUN: Done fetching remote of submodule 'newsubmodule'
32769 0 1 Changing url of submodule 'newsubmodule' from /home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap to /home/ek/repos-msys2/GitPython/git/ext/gitdb
5 1.0 2518.0
4 26.0 2518.0
4 51.0 2518.0
4 76.0 2518.0
4 101.0 2518.0
4 126.0 2518.0
4 152.0 2518.0
4 177.0 2518.0
4 202.0 2518.0
4 227.0 2518.0
4 252.0 2518.0
4 277.0 2518.0
4 303.0 2518.0
4 328.0 2518.0
4 353.0 2518.0
4 378.0 2518.0
4 403.0 2518.0
4 429.0 2518.0
4 454.0 2518.0
4 479.0 2518.0
4 504.0 2518.0
4 529.0 2518.0
4 554.0 2518.0
4 580.0 2518.0
4 605.0 2518.0
4 630.0 2518.0
4 655.0 2518.0
4 680.0 2518.0
4 706.0 2518.0
4 731.0 2518.0
4 756.0 2518.0
4 781.0 2518.0
4 806.0 2518.0
4 831.0 2518.0
4 857.0 2518.0
4 882.0 2518.0
4 907.0 2518.0
4 932.0 2518.0
4 957.0 2518.0
4 983.0 2518.0
4 1008.0 2518.0
4 1033.0 2518.0
4 1058.0 2518.0
4 1083.0 2518.0
4 1108.0 2518.0
4 1134.0 2518.0
4 1159.0 2518.0
4 1184.0 2518.0
4 1209.0 2518.0
4 1234.0 2518.0
4 1259.0 2518.0
4 1285.0 2518.0
4 1310.0 2518.0
4 1335.0 2518.0
4 1360.0 2518.0
4 1385.0 2518.0
4 1411.0 2518.0
4 1436.0 2518.0
4 1461.0 2518.0
4 1486.0 2518.0
4 1511.0 2518.0
4 1536.0 2518.0
4 1562.0 2518.0
4 1587.0 2518.0
4 1612.0 2518.0
4 1637.0 2518.0
4 1662.0 2518.0
4 1688.0 2518.0
4 1713.0 2518.0
4 1738.0 2518.0
4 1763.0 2518.0
4 1788.0 2518.0
4 1813.0 2518.0
4 1839.0 2518.0
4 1864.0 2518.0
4 1889.0 2518.0
4 1914.0 2518.0
4 1939.0 2518.0
4 1965.0 2518.0
4 1990.0 2518.0
4 2015.0 2518.0
4 2040.0 2518.0
4 2065.0 2518.0
4 2090.0 2518.0
4 2116.0 2518.0
4 2141.0 2518.0
4 2166.0 2518.0
4 2191.0 2518.0
4 2216.0 2518.0
4 2242.0 2518.0
4 2267.0 2518.0
4 2292.0 2518.0
4 2317.0 2518.0
4 2342.0 2518.0
4 2367.0 2518.0
4 2393.0 2518.0
4 2418.0 2518.0
4 2443.0 2518.0
4 2468.0 2518.0
4 2493.0 2518.0
4 2518.0 2518.0
6 2518.0 2518.0
9 1.0 904.0
8 10.0 904.0
8 19.0 904.0
8 28.0 904.0
8 37.0 904.0
8 46.0 904.0
8 55.0 904.0
8 64.0 904.0
8 73.0 904.0
8 82.0 904.0
8 91.0 904.0
8 100.0 904.0
8 109.0 904.0
8 118.0 904.0
8 127.0 904.0
8 136.0 904.0
8 145.0 904.0
8 154.0 904.0
8 163.0 904.0
8 172.0 904.0
8 181.0 904.0
8 190.0 904.0
8 199.0 904.0
8 208.0 904.0
8 217.0 904.0
8 226.0 904.0
8 236.0 904.0
8 245.0 904.0
8 254.0 904.0
8 263.0 904.0
8 272.0 904.0
8 281.0 904.0
8 290.0 904.0
8 299.0 904.0
8 308.0 904.0
8 317.0 904.0
8 326.0 904.0
8 335.0 904.0
8 344.0 904.0
8 353.0 904.0
8 362.0 904.0
8 371.0 904.0
8 380.0 904.0
8 389.0 904.0
8 398.0 904.0
8 407.0 904.0
8 416.0 904.0
8 425.0 904.0
8 434.0 904.0
8 443.0 904.0
8 452.0 904.0
8 462.0 904.0
8 471.0 904.0
8 480.0 904.0
8 489.0 904.0
8 498.0 904.0
8 507.0 904.0
8 516.0 904.0
8 525.0 904.0
8 534.0 904.0
8 543.0 904.0
8 552.0 904.0
8 561.0 904.0
8 570.0 904.0
8 579.0 904.0
8 588.0 904.0
8 597.0 904.0
8 606.0 904.0
8 615.0 904.0
8 624.0 904.0
8 633.0 904.0
8 642.0 904.0
8 651.0 904.0
8 660.0 904.0
8 669.0 904.0
8 678.0 904.0
8 688.0 904.0
8 697.0 904.0
8 706.0 904.0
8 715.0 904.0
8 724.0 904.0
8 733.0 904.0
8 742.0 904.0
8 751.0 904.0
8 760.0 904.0
8 769.0 904.0
8 778.0 904.0
8 787.0 904.0
8 796.0 904.0
8 805.0 904.0
8 814.0 904.0
8 823.0 904.0
8 832.0 904.0
8 841.0 904.0
8 850.0 904.0
8 859.0 904.0
8 868.0 904.0
8 877.0 904.0
8 886.0 904.0
8 895.0 904.0
8 904.0 904.0
10 904.0 904.0
33 1.0 2518.0
32 26.0 2518.0
32 51.0 2518.0
32 76.0 2518.0
32 101.0 2518.0
32 126.0 2518.0
32 152.0 2518.0
32 177.0 2518.0
32 202.0 2518.0
32 227.0 2518.0
32 252.0 2518.0
32 277.0 2518.0
32 303.0 2518.0
32 328.0 2518.0
32 353.0 2518.0
32 378.0 2518.0
32 403.0 2518.0
32 429.0 2518.0
32 454.0 2518.0
32 479.0 2518.0
32 504.0 2518.0
32 529.0 2518.0
32 554.0 2518.0
32 580.0 2518.0
32 605.0 2518.0
32 630.0 2518.0
32 655.0 2518.0
32 680.0 2518.0
32 706.0 2518.0
32 731.0 2518.0
32 756.0 2518.0
32 781.0 2518.0
32 806.0 2518.0
32 831.0 2518.0
32 857.0 2518.0
32 882.0 2518.0
32 907.0 2518.0
32 932.0 2518.0
32 957.0 2518.0
32 983.0 2518.0
32 1008.0 2518.0
32 1033.0 2518.0
32 1058.0 2518.0
32 1083.0 2518.0
32 1108.0 2518.0
32 1134.0 2518.0
32 1159.0 2518.0
32 1184.0 2518.0
32 1209.0 2518.0
32 1234.0 2518.0
32 1259.0 2518.0
32 1285.0 2518.0
32 1310.0 2518.0
32 1335.0 2518.0
32 1360.0 2518.0
32 1385.0 2518.0
32 1411.0 2518.0
32 1436.0 2518.0
32 1461.0 2518.0
32 1486.0 2518.0
32 1511.0 2518.0
32 1536.0 2518.0
32 1562.0 2518.0
32 1587.0 2518.0
32 1612.0 2518.0
32 1637.0 2518.0
32 1662.0 2518.0
32 1688.0 2518.0
32 1713.0 2518.0
32 1738.0 2518.0
32 1763.0 2518.0
32 1788.0 2518.0
32 1813.0 2518.0
32 1839.0 2518.0
32 1864.0 2518.0
32 1889.0 2518.0
32 1914.0 2518.0
32 1939.0 2518.0
32 1965.0 2518.0
32 1990.0 2518.0
32 2015.0 2518.0
32 2040.0 2518.0
32 2065.0 2518.0
32 2090.0 2518.0
32 2116.0 2518.0
32 2141.0 2518.0
32 2166.0 2518.0
32 2191.0 2518.0
32 2216.0 2518.0
32 2242.0 2518.0
32 2267.0 2518.0
32 2292.0 2518.0
32 2317.0 2518.0
32 2342.0 2518.0
32 2367.0 2518.0
32 2393.0 2518.0
32 2418.0 2518.0
32 2443.0 2518.0
32 2468.0 2518.0
32 2493.0 2518.0
32 2518.0 2518.0
34 2518.0 2518.0 1.34 MiB | 21.51 MiB/s
65 0.0 1534.0
64 19.0 1534.0
64 33.0 1534.0
64 47.0 1534.0
64 63.0 1534.0
64 77.0 1534.0
64 95.0 1534.0
64 110.0 1534.0
64 123.0 1534.0
64 139.0 1534.0
64 156.0 1534.0
64 171.0 1534.0
64 186.0 1534.0
64 201.0 1534.0
64 216.0 1534.0
64 231.0 1534.0
64 246.0 1534.0
64 264.0 1534.0
64 277.0 1534.0
64 292.0 1534.0
64 308.0 1534.0
64 323.0 1534.0
64 338.0 1534.0
64 353.0 1534.0
64 369.0 1534.0
64 385.0 1534.0
64 399.0 1534.0
64 415.0 1534.0
64 430.0 1534.0
64 447.0 1534.0
64 461.0 1534.0
64 477.0 1534.0
64 491.0 1534.0
64 507.0 1534.0
64 522.0 1534.0
64 537.0 1534.0
64 553.0 1534.0
64 568.0 1534.0
64 584.0 1534.0
64 600.0 1534.0
64 614.0 1534.0
64 629.0 1534.0
64 645.0 1534.0
64 660.0 1534.0
64 675.0 1534.0
64 691.0 1534.0
64 706.0 1534.0
64 722.0 1534.0
64 737.0 1534.0
64 752.0 1534.0
64 767.0 1534.0
64 783.0 1534.0
64 798.0 1534.0
64 814.0 1534.0
64 829.0 1534.0
64 844.0 1534.0
64 860.0 1534.0
64 875.0 1534.0
64 890.0 1534.0
64 908.0 1534.0
64 922.0 1534.0
64 936.0 1534.0
64 954.0 1534.0
64 967.0 1534.0
64 983.0 1534.0
64 998.0 1534.0
64 1017.0 1534.0
64 1028.0 1534.0
64 1044.0 1534.0
64 1059.0 1534.0
64 1074.0 1534.0
64 1090.0 1534.0
64 1105.0 1534.0
64 1120.0 1534.0
64 1137.0 1534.0
64 1152.0 1534.0
64 1166.0 1534.0
64 1182.0 1534.0
64 1197.0 1534.0
64 1212.0 1534.0
64 1228.0 1534.0
64 1243.0 1534.0
64 1258.0 1534.0
64 1275.0 1534.0
64 1294.0 1534.0
64 1304.0 1534.0
64 1320.0 1534.0
64 1336.0 1534.0
64 1350.0 1534.0
64 1366.0 1534.0
64 1382.0 1534.0
64 1400.0 1534.0
64 1412.0 1534.0
64 1428.0 1534.0
64 1443.0 1534.0
64 1458.0 1534.0
64 1473.0 1534.0
64 1489.0 1534.0
64 1505.0 1534.0
64 1519.0 1534.0
64 1534.0 1534.0
66 1534.0 1534.0
32770 0 1 Done adjusting url of submodule 'newsubmodule'
1025 0 1 Fetching remote origin of submodule 'newsubmodule'
1027 0 1 Done fetching remote of submodule 'newsubmodule'
2049 0 1 Updating working tree at submrepo for submodule 'newsubmodule' to revision 775cfe8299ea5474f605935469359a9d1cdb49dc
2050 0 1 Done updating working tree for submodule 'newsubmodule'
16385 0 1 DRY-RUN: Changing branch of submodule 'newsubmodule' from refs/heads/some_virtual_branch to refs/heads/master
16386 0 1 DRY-RUN: Done changing branch of submodule 'newsubmodule'
1025 0 1 DRY-RUN: Fetching remote origin of submodule 'newsubmodule'
1027 0 1 DRY-RUN: Done fetching remote of submodule 'newsubmodule'
16385 0 1 Changing branch of submodule 'newsubmodule' from refs/heads/some_virtual_branch to refs/heads/master
16386 0 1 Done changing branch of submodule 'newsubmodule'
1025 0 1 Fetching remote origin of submodule 'newsubmodule'
1027 0 1 Done fetching remote of submodule 'newsubmodule'
16385 0 1 DRY-RUN: Changing branch of submodule 'newsubmodule' from refs/heads/some_virtual_branch to refs/heads/master
16386 0 1 DRY-RUN: Done changing branch of submodule 'newsubmodule'
1025 0 1 DRY-RUN: Fetching remote origin of submodule 'newsubmodule'
1027 0 1 DRY-RUN: Done fetching remote of submodule 'newsubmodule'
513 0 1 DRY-RUN: Cloning url '/home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap' to '/tmp/non_bare_test_root_modulert2m1s_2/submrepo/gitdb/ext/smmap' in submodule 'smmap'
514 0 1 DRY-RUN: Done cloning to /tmp/non_bare_test_root_modulert2m1s_2/submrepo/gitdb/ext/smmap
16385 0 1 Changing branch of submodule 'newsubmodule' from refs/heads/some_virtual_branch to refs/heads/master
16386 0 1 Done changing branch of submodule 'newsubmodule'
1025 0 1 Fetching remote origin of submodule 'newsubmodule'
1027 0 1 Done fetching remote of submodule 'newsubmodule'
513 0 1 Cloning url '/home/ek/repos-msys2/GitPython/git/ext/gitdb/gitdb/ext/smmap' to '/tmp/non_bare_test_root_modulert2m1s_2/submrepo/gitdb/ext/smmap' in submodule 'smmap'
514 0 1 Done cloning to /tmp/non_bare_test_root_modulert2m1s_2/submrepo/gitdb/ext/smmap
2049 0 1 Updating working tree at gitdb/ext/smmap for submodule 'smmap' to revision f31bfa378c8840d38d31e7e11ef2b84f191a491e
2050 0 1 Done updating working tree for submodule 'smmap'
-------------------------------------------------- Captured log call ---------------------------------------------------
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.objects.submodule.root:root.py:345 Current sha f31bfa378c8840d38d31e7e11ef2b84f191a491e was not contained in the tracking branch at the new remote, setting it the the remote's tracking branch
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
WARNING git.remote:remote.py:904 Error lines received while fetching: fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
fatal: 'bar' does not appear to be a git repository
fatal: Could not read from remote repository.
---------- coverage: platform cygwin, python 3.12.8-final-0 ----------
Name Stmts Miss Cover
-------------------------------------------------------
git/__init__.py 51 6 88%
git/cmd.py 601 85 86%
git/compat.py 62 15 76%
git/config.py 441 47 89%
git/db.py 26 1 96%
git/diff.py 290 9 97%
git/exc.py 66 3 95%
git/index/__init__.py 3 0 100%
git/index/base.py 500 93 81%
git/index/fun.py 188 14 93%
git/index/typ.py 76 3 96%
git/index/util.py 52 2 96%
git/objects/__init__.py 7 0 100%
git/objects/base.py 91 10 89%
git/objects/blob.py 20 1 95%
git/objects/commit.py 319 28 91%
git/objects/fun.py 99 3 97%
git/objects/submodule/__init__.py 3 0 100%
git/objects/submodule/base.py 578 37 94%
git/objects/submodule/root.py 136 12 91%
git/objects/submodule/util.py 50 8 84%
git/objects/tag.py 48 12 75%
git/objects/tree.py 150 27 82%
git/objects/util.py 228 23 90%
git/refs/__init__.py 7 0 100%
git/refs/head.py 98 6 94%
git/refs/log.py 150 10 93%
git/refs/reference.py 55 3 95%
git/refs/remote.py 32 2 94%
git/refs/symbolic.py 369 20 95%
git/refs/tag.py 47 5 89%
git/remote.py 461 54 88%
git/repo/__init__.py 2 0 100%
git/repo/base.py 587 32 95%
git/repo/fun.py 204 19 91%
git/types.py 65 7 89%
git/util.py 559 84 85%
-------------------------------------------------------
TOTAL 6721 681 90%
=============================================== short test summary info ================================================
SKIPPED [1] test/test_config.py:91: Known failure -- included values are not in effect right away
SKIPPED [1] test/test_git.py:248: The regression only affected Windows, and this test logic is OS-specific.
SKIPPED [1] test/test_repo.py:261: The referenced repository was removed, and one needs to set up a new
password controlled repo under the org's control.
SKIPPED [1] test/test_submodule.py:1075: Specifically for Windows.
SKIPPED [1] test/test_util.py:88: Cygwin can't set the permissions that make the test meaningful.
SKIPPED [1] test/test_util.py:111: Cygwin can't set the permissions that make the test meaningful.
SKIPPED [1] test/test_util.py:152: PermissionError is only ever wrapped on Windows
SKIPPED [2] test/test_util.py:164: Cygwin can't set the permissions that make the test meaningful.
SKIPPED [30] test/test_util.py:217: These environment variables are only used on Windows.
xfail test/test_submodule.py::TestSubmodule::test_depth - for some unknown reason the assertion fails, even though it in fact is working in more common setup
xfail test/test_util.py::TestCygpath::test_cygpath_ok[C:\\Users-/cygdrive/c/Users] - Returns: '/proc/cygdrive/c/Users'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[C:\\d/e-/cygdrive/c/d/e] - Returns: '/proc/cygdrive/c/d/e'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[C:\\-/cygdrive/c/] - Returns: '/proc/cygdrive/c/'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[\\\\server\\BAR/-//server/BAR/] - Returns: '//server/BAR'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[D:/Apps-/cygdrive/d/Apps] - Returns: '/proc/cygdrive/d/Apps'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[D:/Apps\\fOO-/cygdrive/d/Apps/fOO] - Returns: '/proc/cygdrive/d/Apps/fOO'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[D:\\Apps/123-/cygdrive/d/Apps/123] - Returns: '/proc/cygdrive/d/Apps/123'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[\\\\?\\a:\\com-/cygdrive/a/com] - Returns: '/proc/cygdrive/a/com'
xfail test/test_util.py::TestCygpath::test_cygpath_ok[\\\\?\\a:/com-/cygdrive/a/com] - Returns: '/proc/cygdrive/a/com'
xfail test/test_util.py::TestCygpath::test_cygpath_norm_ok[.\\bar-bar] - Returns: './bar'
XPASS test/test_docs.py::Tutorials::test_submodules - Cygwin GitPython can't find SHA for submodule
XPASS test/test_repo.py::TestRepo::test_submodules - Cygwin GitPython can't find submodule SHA
XPASS test/test_submodule.py::TestSubmodule::test_root_module - Cygwin GitPython can't find submodule SHA
FAILED test/test_index.py::TestIndex::test_index_mutation - FileNotFoundError: [Errno 2] No such file or directory: '/etc/nonexisting' -> '/tmp/non_bare_test_index_mutationonp...
Results (1164.88s (0:19:24)):
614 passed
3 xpassed
1 failed
- test/test_index.py:558 TestIndex.test_index_mutation
11 xfailed
39 skipped
(.venv)
ek@Glub MSYS ~/repos-msys2/GitPython
$ echo "$?"
1
(.venv)
ek@Glub MSYS ~/repos-msys2/GitPython
$ python
Python 3.12.8 (main, Dec 17 2024, 07:52:53) [GCC 13.3.0] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment