Last active
January 5, 2025 05:31
-
-
Save EliahKagan/20042f202d94e3af5733082abcac2182 to your computer and use it in GitHub Desktop.
GitPython test run using an MSYS2 build of Python 3.12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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