Spaces:
Runtime error
Runtime error
TheDavidYoungblood
commited on
Commit
•
99b6299
1
Parent(s):
a181a43
Init-Commit v3-postupdates
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitignore +65 -0
- ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/AUTHORS +0 -58
- ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/INSTALLER +0 -1
- ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/LICENSE +0 -29
- ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/METADATA +0 -297
- ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/RECORD +0 -83
- ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/REQUESTED +0 -0
- ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/WHEEL +0 -5
- ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/top_level.txt +0 -1
- ILYA/Lib/site-packages/_distutils_hack/__pycache__/__init__.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/_distutils_hack/__pycache__/override.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/__init__.py +0 -300
- ILYA/Lib/site-packages/git/__pycache__/__init__.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/__pycache__/cmd.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/__pycache__/compat.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/__pycache__/config.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/__pycache__/db.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/__pycache__/diff.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/__pycache__/exc.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/__pycache__/remote.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/__pycache__/types.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/__pycache__/util.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/cmd.py +0 -1723
- ILYA/Lib/site-packages/git/compat.py +0 -165
- ILYA/Lib/site-packages/git/config.py +0 -944
- ILYA/Lib/site-packages/git/db.py +0 -71
- ILYA/Lib/site-packages/git/diff.py +0 -775
- ILYA/Lib/site-packages/git/exc.py +0 -228
- ILYA/Lib/site-packages/git/index/__init__.py +0 -16
- ILYA/Lib/site-packages/git/index/__pycache__/__init__.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/index/__pycache__/base.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/index/__pycache__/fun.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/index/__pycache__/typ.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/index/__pycache__/util.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/index/base.py +0 -1518
- ILYA/Lib/site-packages/git/index/fun.py +0 -465
- ILYA/Lib/site-packages/git/index/typ.py +0 -202
- ILYA/Lib/site-packages/git/index/util.py +0 -121
- ILYA/Lib/site-packages/git/objects/__init__.py +0 -25
- ILYA/Lib/site-packages/git/objects/__pycache__/__init__.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/objects/__pycache__/base.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/objects/__pycache__/blob.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/objects/__pycache__/commit.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/objects/__pycache__/fun.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/objects/__pycache__/tag.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/objects/__pycache__/tree.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/objects/__pycache__/util.cpython-311.pyc +0 -0
- ILYA/Lib/site-packages/git/objects/base.py +0 -301
- ILYA/Lib/site-packages/git/objects/blob.py +0 -48
- ILYA/Lib/site-packages/git/objects/commit.py +0 -899
.gitignore
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Ignore environment variable files
|
2 |
+
.env
|
3 |
+
|
4 |
+
# Ignore Python bytecode
|
5 |
+
__pycache__/
|
6 |
+
*.py[cod]
|
7 |
+
*$py.class
|
8 |
+
|
9 |
+
# Ignore virtual environment directories
|
10 |
+
venv/
|
11 |
+
env/
|
12 |
+
.venv/
|
13 |
+
.env/
|
14 |
+
Include/
|
15 |
+
Lib/
|
16 |
+
Library/
|
17 |
+
Scripts/
|
18 |
+
Share/
|
19 |
+
|
20 |
+
# Ignore Jupyter Notebook checkpoints
|
21 |
+
.ipynb_checkpoints/
|
22 |
+
|
23 |
+
# Ignore macOS system files
|
24 |
+
.DS_Store
|
25 |
+
|
26 |
+
# Ignore temporary files created by editors
|
27 |
+
*.swp
|
28 |
+
*.swo
|
29 |
+
*~
|
30 |
+
*.bak
|
31 |
+
|
32 |
+
# Ignore logs
|
33 |
+
*.log
|
34 |
+
|
35 |
+
# Ignore coverage reports
|
36 |
+
htmlcov/
|
37 |
+
.coverage
|
38 |
+
.coverage.*
|
39 |
+
|
40 |
+
# Ignore mypy cache
|
41 |
+
.mypy_cache/
|
42 |
+
.dmypy.json
|
43 |
+
dmypy.json
|
44 |
+
|
45 |
+
# Ignore Pytest cache
|
46 |
+
.pytest_cache/
|
47 |
+
|
48 |
+
# Ignore temporary files
|
49 |
+
*.tmp
|
50 |
+
*.temp
|
51 |
+
|
52 |
+
# Ignore compiled code
|
53 |
+
*.so
|
54 |
+
*.dylib
|
55 |
+
*.dll
|
56 |
+
|
57 |
+
# Ignore package directories
|
58 |
+
node_modules/
|
59 |
+
build/
|
60 |
+
dist/
|
61 |
+
*.egg-info/
|
62 |
+
|
63 |
+
# Ignore Hugging Face Spaces outputs
|
64 |
+
output/
|
65 |
+
runs/
|
ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/AUTHORS
DELETED
@@ -1,58 +0,0 @@
|
|
1 |
-
GitPython was originally written by Michael Trier.
|
2 |
-
GitPython 0.2 was partially (re)written by Sebastian Thiel, based on 0.1.6 and git-dulwich.
|
3 |
-
|
4 |
-
Contributors are:
|
5 |
-
|
6 |
-
-Michael Trier <mtrier _at_ gmail.com>
|
7 |
-
-Alan Briolat
|
8 |
-
-Florian Apolloner <florian _at_ apolloner.eu>
|
9 |
-
-David Aguilar <davvid _at_ gmail.com>
|
10 |
-
-Jelmer Vernooij <jelmer _at_ samba.org>
|
11 |
-
-Steve Frécinaux <code _at_ istique.net>
|
12 |
-
-Kai Lautaportti <kai _at_ lautaportti.fi>
|
13 |
-
-Paul Sowden <paul _at_ idontsmoke.co.uk>
|
14 |
-
-Sebastian Thiel <byronimo _at_ gmail.com>
|
15 |
-
-Jonathan Chu <jonathan.chu _at_ me.com>
|
16 |
-
-Vincent Driessen <me _at_ nvie.com>
|
17 |
-
-Phil Elson <pelson _dot_ pub _at_ gmail.com>
|
18 |
-
-Bernard `Guyzmo` Pratz <[email protected]>
|
19 |
-
-Timothy B. Hartman <tbhartman _at_ gmail.com>
|
20 |
-
-Konstantin Popov <konstantin.popov.89 _at_ yandex.ru>
|
21 |
-
-Peter Jones <pjones _at_ redhat.com>
|
22 |
-
-Anson Mansfield <anson.mansfield _at_ gmail.com>
|
23 |
-
-Ken Odegard <ken.odegard _at_ gmail.com>
|
24 |
-
-Alexis Horgix Chotard
|
25 |
-
-Piotr Babij <piotr.babij _at_ gmail.com>
|
26 |
-
-Mikuláš Poul <mikulaspoul _at_ gmail.com>
|
27 |
-
-Charles Bouchard-Légaré <cblegare.atl _at_ ntis.ca>
|
28 |
-
-Yaroslav Halchenko <debian _at_ onerussian.com>
|
29 |
-
-Tim Swast <swast _at_ google.com>
|
30 |
-
-William Luc Ritchie
|
31 |
-
-David Host <hostdm _at_ outlook.com>
|
32 |
-
-A. Jesse Jiryu Davis <jesse _at_ emptysquare.net>
|
33 |
-
-Steven Whitman <ninloot _at_ gmail.com>
|
34 |
-
-Stefan Stancu <stefan.stancu _at_ gmail.com>
|
35 |
-
-César Izurieta <cesar _at_ caih.org>
|
36 |
-
-Arthur Milchior <arthur _at_ milchior.fr>
|
37 |
-
-Anil Khatri <anil.soccer.khatri _at_ gmail.com>
|
38 |
-
-JJ Graham <thetwoj _at_ gmail.com>
|
39 |
-
-Ben Thayer <ben _at_ benthayer.com>
|
40 |
-
-Dries Kennes <admin _at_ dries007.net>
|
41 |
-
-Pratik Anurag <panurag247365 _at_ gmail.com>
|
42 |
-
-Harmon <harmon.public _at_ gmail.com>
|
43 |
-
-Liam Beguin <liambeguin _at_ gmail.com>
|
44 |
-
-Ram Rachum <ram _at_ rachum.com>
|
45 |
-
-Alba Mendez <me _at_ alba.sh>
|
46 |
-
-Robert Westman <robert _at_ byteflux.io>
|
47 |
-
-Hugo van Kemenade
|
48 |
-
-Hiroki Tokunaga <tokusan441 _at_ gmail.com>
|
49 |
-
-Julien Mauroy <pro.julien.mauroy _at_ gmail.com>
|
50 |
-
-Patrick Gerard
|
51 |
-
-Luke Twist <[email protected]>
|
52 |
-
-Joseph Hale <me _at_ jhale.dev>
|
53 |
-
-Santos Gallegos <stsewd _at_ proton.me>
|
54 |
-
-Wenhan Zhu <wzhu.cosmos _at_ gmail.com>
|
55 |
-
-Eliah Kagan <eliah.kagan _at_ gmail.com>
|
56 |
-
-Ethan Lin <et.repositories _at_ gmail.com>
|
57 |
-
|
58 |
-
Portions derived from other open source works and are clearly marked.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/INSTALLER
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
pip
|
|
|
|
ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/LICENSE
DELETED
@@ -1,29 +0,0 @@
|
|
1 |
-
Copyright (C) 2008, 2009 Michael Trier and contributors
|
2 |
-
All rights reserved.
|
3 |
-
|
4 |
-
Redistribution and use in source and binary forms, with or without
|
5 |
-
modification, are permitted provided that the following conditions
|
6 |
-
are met:
|
7 |
-
|
8 |
-
* Redistributions of source code must retain the above copyright
|
9 |
-
notice, this list of conditions and the following disclaimer.
|
10 |
-
|
11 |
-
* Redistributions in binary form must reproduce the above copyright
|
12 |
-
notice, this list of conditions and the following disclaimer in the
|
13 |
-
documentation and/or other materials provided with the distribution.
|
14 |
-
|
15 |
-
* Neither the name of the GitPython project nor the names of
|
16 |
-
its contributors may be used to endorse or promote products derived
|
17 |
-
from this software without specific prior written permission.
|
18 |
-
|
19 |
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
20 |
-
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
21 |
-
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
22 |
-
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
23 |
-
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
24 |
-
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
25 |
-
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
26 |
-
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
27 |
-
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
28 |
-
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
29 |
-
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/METADATA
DELETED
@@ -1,297 +0,0 @@
|
|
1 |
-
Metadata-Version: 2.1
|
2 |
-
Name: GitPython
|
3 |
-
Version: 3.1.43
|
4 |
-
Summary: GitPython is a Python library used to interact with Git repositories
|
5 |
-
Home-page: https://github.com/gitpython-developers/GitPython
|
6 |
-
Author: Sebastian Thiel, Michael Trier
|
7 |
-
Author-email: [email protected], [email protected]
|
8 |
-
License: BSD-3-Clause
|
9 |
-
Classifier: Development Status :: 5 - Production/Stable
|
10 |
-
Classifier: Environment :: Console
|
11 |
-
Classifier: Intended Audience :: Developers
|
12 |
-
Classifier: License :: OSI Approved :: BSD License
|
13 |
-
Classifier: Operating System :: OS Independent
|
14 |
-
Classifier: Operating System :: POSIX
|
15 |
-
Classifier: Operating System :: Microsoft :: Windows
|
16 |
-
Classifier: Operating System :: MacOS :: MacOS X
|
17 |
-
Classifier: Typing :: Typed
|
18 |
-
Classifier: Programming Language :: Python
|
19 |
-
Classifier: Programming Language :: Python :: 3
|
20 |
-
Classifier: Programming Language :: Python :: 3.7
|
21 |
-
Classifier: Programming Language :: Python :: 3.8
|
22 |
-
Classifier: Programming Language :: Python :: 3.9
|
23 |
-
Classifier: Programming Language :: Python :: 3.10
|
24 |
-
Classifier: Programming Language :: Python :: 3.11
|
25 |
-
Classifier: Programming Language :: Python :: 3.12
|
26 |
-
Requires-Python: >=3.7
|
27 |
-
Description-Content-Type: text/markdown
|
28 |
-
License-File: LICENSE
|
29 |
-
License-File: AUTHORS
|
30 |
-
Requires-Dist: gitdb <5,>=4.0.1
|
31 |
-
Requires-Dist: typing-extensions >=3.7.4.3 ; python_version < "3.8"
|
32 |
-
Provides-Extra: doc
|
33 |
-
Requires-Dist: sphinx ==4.3.2 ; extra == 'doc'
|
34 |
-
Requires-Dist: sphinx-rtd-theme ; extra == 'doc'
|
35 |
-
Requires-Dist: sphinxcontrib-applehelp <=1.0.4,>=1.0.2 ; extra == 'doc'
|
36 |
-
Requires-Dist: sphinxcontrib-devhelp ==1.0.2 ; extra == 'doc'
|
37 |
-
Requires-Dist: sphinxcontrib-htmlhelp <=2.0.1,>=2.0.0 ; extra == 'doc'
|
38 |
-
Requires-Dist: sphinxcontrib-qthelp ==1.0.3 ; extra == 'doc'
|
39 |
-
Requires-Dist: sphinxcontrib-serializinghtml ==1.1.5 ; extra == 'doc'
|
40 |
-
Requires-Dist: sphinx-autodoc-typehints ; extra == 'doc'
|
41 |
-
Provides-Extra: test
|
42 |
-
Requires-Dist: coverage[toml] ; extra == 'test'
|
43 |
-
Requires-Dist: ddt !=1.4.3,>=1.1.1 ; extra == 'test'
|
44 |
-
Requires-Dist: mypy ; extra == 'test'
|
45 |
-
Requires-Dist: pre-commit ; extra == 'test'
|
46 |
-
Requires-Dist: pytest >=7.3.1 ; extra == 'test'
|
47 |
-
Requires-Dist: pytest-cov ; extra == 'test'
|
48 |
-
Requires-Dist: pytest-instafail ; extra == 'test'
|
49 |
-
Requires-Dist: pytest-mock ; extra == 'test'
|
50 |
-
Requires-Dist: pytest-sugar ; extra == 'test'
|
51 |
-
Requires-Dist: typing-extensions ; (python_version < "3.11") and extra == 'test'
|
52 |
-
Requires-Dist: mock ; (python_version < "3.8") and extra == 'test'
|
53 |
-
|
54 |
-
![Python package](https://github.com/gitpython-developers/GitPython/workflows/Python%20package/badge.svg)
|
55 |
-
[![Documentation Status](https://readthedocs.org/projects/gitpython/badge/?version=stable)](https://readthedocs.org/projects/gitpython/?badge=stable)
|
56 |
-
[![Packaging status](https://repology.org/badge/tiny-repos/python:gitpython.svg)](https://repology.org/metapackage/python:gitpython/versions)
|
57 |
-
|
58 |
-
## [Gitoxide](https://github.com/Byron/gitoxide): A peek into the future…
|
59 |
-
|
60 |
-
I started working on GitPython in 2009, back in the days when Python was 'my thing' and I had great plans with it.
|
61 |
-
Of course, back in the days, I didn't really know what I was doing and this shows in many places. Somewhat similar to
|
62 |
-
Python this happens to be 'good enough', but at the same time is deeply flawed and broken beyond repair.
|
63 |
-
|
64 |
-
By now, GitPython is widely used and I am sure there is a good reason for that, it's something to be proud of and happy about.
|
65 |
-
The community is maintaining the software and is keeping it relevant for which I am absolutely grateful. For the time to come I am happy to continue maintaining GitPython, remaining hopeful that one day it won't be needed anymore.
|
66 |
-
|
67 |
-
More than 15 years after my first meeting with 'git' I am still in excited about it, and am happy to finally have the tools and
|
68 |
-
probably the skills to scratch that itch of mine: implement `git` in a way that makes tool creation a piece of cake for most.
|
69 |
-
|
70 |
-
If you like the idea and want to learn more, please head over to [gitoxide](https://github.com/Byron/gitoxide), an
|
71 |
-
implementation of 'git' in [Rust](https://www.rust-lang.org).
|
72 |
-
|
73 |
-
*(Please note that `gitoxide` is not currently available for use in Python, and that Rust is required.)*
|
74 |
-
|
75 |
-
## GitPython
|
76 |
-
|
77 |
-
GitPython is a python library used to interact with git repositories, high-level like git-porcelain,
|
78 |
-
or low-level like git-plumbing.
|
79 |
-
|
80 |
-
It provides abstractions of git objects for easy access of repository data often backed by calling the `git`
|
81 |
-
command-line program.
|
82 |
-
|
83 |
-
### DEVELOPMENT STATUS
|
84 |
-
|
85 |
-
This project is in **maintenance mode**, which means that
|
86 |
-
|
87 |
-
- …there will be no feature development, unless these are contributed
|
88 |
-
- …there will be no bug fixes, unless they are relevant to the safety of users, or contributed
|
89 |
-
- …issues will be responded to with waiting times of up to a month
|
90 |
-
|
91 |
-
The project is open to contributions of all kinds, as well as new maintainers.
|
92 |
-
|
93 |
-
### REQUIREMENTS
|
94 |
-
|
95 |
-
GitPython needs the `git` executable to be installed on the system and available in your
|
96 |
-
`PATH` for most operations. If it is not in your `PATH`, you can help GitPython find it
|
97 |
-
by setting the `GIT_PYTHON_GIT_EXECUTABLE=<path/to/git>` environment variable.
|
98 |
-
|
99 |
-
- Git (1.7.x or newer)
|
100 |
-
- Python >= 3.7
|
101 |
-
|
102 |
-
The list of dependencies are listed in `./requirements.txt` and `./test-requirements.txt`.
|
103 |
-
The installer takes care of installing them for you.
|
104 |
-
|
105 |
-
### INSTALL
|
106 |
-
|
107 |
-
GitPython and its required package dependencies can be installed in any of the following ways, all of which should typically be done in a [virtual environment](https://docs.python.org/3/tutorial/venv.html).
|
108 |
-
|
109 |
-
#### From PyPI
|
110 |
-
|
111 |
-
To obtain and install a copy [from PyPI](https://pypi.org/project/GitPython/), run:
|
112 |
-
|
113 |
-
```sh
|
114 |
-
pip install GitPython
|
115 |
-
```
|
116 |
-
|
117 |
-
(A distribution package can also be downloaded for manual installation at [the PyPI page](https://pypi.org/project/GitPython/).)
|
118 |
-
|
119 |
-
#### From downloaded source code
|
120 |
-
|
121 |
-
If you have downloaded the source code, run this from inside the unpacked `GitPython` directory:
|
122 |
-
|
123 |
-
```sh
|
124 |
-
pip install .
|
125 |
-
```
|
126 |
-
|
127 |
-
#### By cloning the source code repository
|
128 |
-
|
129 |
-
To clone the [the GitHub repository](https://github.com/gitpython-developers/GitPython) from source to work on the code, you can do it like so:
|
130 |
-
|
131 |
-
```sh
|
132 |
-
git clone https://github.com/gitpython-developers/GitPython
|
133 |
-
cd GitPython
|
134 |
-
./init-tests-after-clone.sh
|
135 |
-
```
|
136 |
-
|
137 |
-
On Windows, `./init-tests-after-clone.sh` can be run in a Git Bash shell.
|
138 |
-
|
139 |
-
If you are cloning [your own fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks), then replace the above `git clone` command with one that gives the URL of your fork. Or use this [`gh`](https://cli.github.com/) command (assuming you have `gh` and your fork is called `GitPython`):
|
140 |
-
|
141 |
-
```sh
|
142 |
-
gh repo clone GitPython
|
143 |
-
```
|
144 |
-
|
145 |
-
Having cloned the repo, create and activate your [virtual environment](https://docs.python.org/3/tutorial/venv.html).
|
146 |
-
|
147 |
-
Then make an [editable install](https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs):
|
148 |
-
|
149 |
-
```sh
|
150 |
-
pip install -e ".[test]"
|
151 |
-
```
|
152 |
-
|
153 |
-
In the less common case that you do not want to install test dependencies, `pip install -e .` can be used instead.
|
154 |
-
|
155 |
-
#### With editable *dependencies* (not preferred, and rarely needed)
|
156 |
-
|
157 |
-
In rare cases, you may want to work on GitPython and one or both of its [gitdb](https://github.com/gitpython-developers/gitdb) and [smmap](https://github.com/gitpython-developers/smmap) dependencies at the same time, with changes in your local working copy of gitdb or smmap immediatley reflected in the behavior of your local working copy of GitPython. This can be done by making editable installations of those dependencies in the same virtual environment where you install GitPython.
|
158 |
-
|
159 |
-
If you want to do that *and* you want the versions in GitPython's git submodules to be used, then pass `-e git/ext/gitdb` and/or `-e git/ext/gitdb/gitdb/ext/smmap` to `pip install`. This can be done in any order, and in separate `pip install` commands or the same one, so long as `-e` appears before *each* path. For example, you can install GitPython, gitdb, and smmap editably in the currently active virtual environment this way:
|
160 |
-
|
161 |
-
```sh
|
162 |
-
pip install -e ".[test]" -e git/ext/gitdb -e git/ext/gitdb/gitdb/ext/smmap
|
163 |
-
```
|
164 |
-
|
165 |
-
The submodules must have been cloned for that to work, but that will already be the case if you have run `./init-tests-after-clone.sh`. You can use `pip list` to check which packages are installed editably and which are installed normally.
|
166 |
-
|
167 |
-
To reiterate, this approach should only rarely be used. For most development it is preferable to allow the gitdb and smmap dependencices to be retrieved automatically from PyPI in their latest stable packaged versions.
|
168 |
-
|
169 |
-
### Limitations
|
170 |
-
|
171 |
-
#### Leakage of System Resources
|
172 |
-
|
173 |
-
GitPython is not suited for long-running processes (like daemons) as it tends to
|
174 |
-
leak system resources. It was written in a time where destructors (as implemented
|
175 |
-
in the `__del__` method) still ran deterministically.
|
176 |
-
|
177 |
-
In case you still want to use it in such a context, you will want to search the
|
178 |
-
codebase for `__del__` implementations and call these yourself when you see fit.
|
179 |
-
|
180 |
-
Another way assure proper cleanup of resources is to factor out GitPython into a
|
181 |
-
separate process which can be dropped periodically.
|
182 |
-
|
183 |
-
#### Windows support
|
184 |
-
|
185 |
-
See [Issue #525](https://github.com/gitpython-developers/GitPython/issues/525).
|
186 |
-
|
187 |
-
### RUNNING TESTS
|
188 |
-
|
189 |
-
_Important_: Right after cloning this repository, please be sure to have executed
|
190 |
-
the `./init-tests-after-clone.sh` script in the repository root. Otherwise
|
191 |
-
you will encounter test failures.
|
192 |
-
|
193 |
-
#### Install test dependencies
|
194 |
-
|
195 |
-
Ensure testing libraries are installed. This is taken care of already if you installed with:
|
196 |
-
|
197 |
-
```sh
|
198 |
-
pip install -e ".[test]"
|
199 |
-
```
|
200 |
-
|
201 |
-
If you had installed with a command like `pip install -e .` instead, you can still run
|
202 |
-
the above command to add the testing dependencies.
|
203 |
-
|
204 |
-
#### Test commands
|
205 |
-
|
206 |
-
To test, run:
|
207 |
-
|
208 |
-
```sh
|
209 |
-
pytest
|
210 |
-
```
|
211 |
-
|
212 |
-
To lint, and apply some linting fixes as well as automatic code formatting, run:
|
213 |
-
|
214 |
-
```sh
|
215 |
-
pre-commit run --all-files
|
216 |
-
```
|
217 |
-
|
218 |
-
This includes the linting and autoformatting done by Ruff, as well as some other checks.
|
219 |
-
|
220 |
-
To typecheck, run:
|
221 |
-
|
222 |
-
```sh
|
223 |
-
mypy
|
224 |
-
```
|
225 |
-
|
226 |
-
#### CI (and tox)
|
227 |
-
|
228 |
-
Style and formatting checks, and running tests on all the different supported Python versions, will be performed:
|
229 |
-
|
230 |
-
- Upon submitting a pull request.
|
231 |
-
- On each push, *if* you have a fork with GitHub Actions enabled.
|
232 |
-
- Locally, if you run [`tox`](https://tox.wiki/) (this skips any Python versions you don't have installed).
|
233 |
-
|
234 |
-
#### Configuration files
|
235 |
-
|
236 |
-
Specific tools are all configured in the `./pyproject.toml` file:
|
237 |
-
|
238 |
-
- `pytest` (test runner)
|
239 |
-
- `coverage.py` (code coverage)
|
240 |
-
- `ruff` (linter and formatter)
|
241 |
-
- `mypy` (type checker)
|
242 |
-
|
243 |
-
Orchestration tools:
|
244 |
-
|
245 |
-
- Configuration for `pre-commit` is in the `./.pre-commit-config.yaml` file.
|
246 |
-
- Configuration for `tox` is in `./tox.ini`.
|
247 |
-
- Configuration for GitHub Actions (CI) is in files inside `./.github/workflows/`.
|
248 |
-
|
249 |
-
### Contributions
|
250 |
-
|
251 |
-
Please have a look at the [contributions file][contributing].
|
252 |
-
|
253 |
-
### INFRASTRUCTURE
|
254 |
-
|
255 |
-
- [User Documentation](http://gitpython.readthedocs.org)
|
256 |
-
- [Questions and Answers](http://stackexchange.com/filters/167317/gitpython)
|
257 |
-
- Please post on Stack Overflow and use the `gitpython` tag
|
258 |
-
- [Issue Tracker](https://github.com/gitpython-developers/GitPython/issues)
|
259 |
-
- Post reproducible bugs and feature requests as a new issue.
|
260 |
-
Please be sure to provide the following information if posting bugs:
|
261 |
-
- GitPython version (e.g. `import git; git.__version__`)
|
262 |
-
- Python version (e.g. `python --version`)
|
263 |
-
- The encountered stack-trace, if applicable
|
264 |
-
- Enough information to allow reproducing the issue
|
265 |
-
|
266 |
-
### How to make a new release
|
267 |
-
|
268 |
-
1. Update/verify the **version** in the `VERSION` file.
|
269 |
-
2. Update/verify that the `doc/source/changes.rst` changelog file was updated. It should include a link to the forthcoming release page: `https://github.com/gitpython-developers/GitPython/releases/tag/<version>`
|
270 |
-
3. Commit everything.
|
271 |
-
4. Run `git tag -s <version>` to tag the version in Git.
|
272 |
-
5. _Optionally_ create and activate a [virtual environment](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment). (Then the next step can install `build` and `twine`.)
|
273 |
-
6. Run `make release`.
|
274 |
-
7. Go to [GitHub Releases](https://github.com/gitpython-developers/GitPython/releases) and publish a new one with the recently pushed tag. Generate the changelog.
|
275 |
-
|
276 |
-
### Projects using GitPython
|
277 |
-
|
278 |
-
- [PyDriller](https://github.com/ishepard/pydriller)
|
279 |
-
- [Kivy Designer](https://github.com/kivy/kivy-designer)
|
280 |
-
- [Prowl](https://github.com/nettitude/Prowl)
|
281 |
-
- [Python Taint](https://github.com/python-security/pyt)
|
282 |
-
- [Buster](https://github.com/axitkhurana/buster)
|
283 |
-
- [git-ftp](https://github.com/ezyang/git-ftp)
|
284 |
-
- [Git-Pandas](https://github.com/wdm0006/git-pandas)
|
285 |
-
- [PyGitUp](https://github.com/msiemens/PyGitUp)
|
286 |
-
- [PyJFuzz](https://github.com/mseclab/PyJFuzz)
|
287 |
-
- [Loki](https://github.com/Neo23x0/Loki)
|
288 |
-
- [Omniwallet](https://github.com/OmniLayer/omniwallet)
|
289 |
-
- [GitViper](https://github.com/BeayemX/GitViper)
|
290 |
-
- [Git Gud](https://github.com/bthayer2365/git-gud)
|
291 |
-
|
292 |
-
### LICENSE
|
293 |
-
|
294 |
-
[3-Clause BSD License](https://opensource.org/license/bsd-3-clause/), also known as the New BSD License. See the [LICENSE file][license].
|
295 |
-
|
296 |
-
[contributing]: https://github.com/gitpython-developers/GitPython/blob/main/CONTRIBUTING.md
|
297 |
-
[license]: https://github.com/gitpython-developers/GitPython/blob/main/LICENSE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/RECORD
DELETED
@@ -1,83 +0,0 @@
|
|
1 |
-
GitPython-3.1.43.dist-info/AUTHORS,sha256=h1TlPKfp05GA1eKQ15Yl4biR0C0FgivuGSeRA6Q1dz0,2286
|
2 |
-
GitPython-3.1.43.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
3 |
-
GitPython-3.1.43.dist-info/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
|
4 |
-
GitPython-3.1.43.dist-info/METADATA,sha256=sAh3r1BMVw5_olGgDmpMS69zBpVr7UEOeRivNHKznfU,13376
|
5 |
-
GitPython-3.1.43.dist-info/RECORD,,
|
6 |
-
GitPython-3.1.43.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7 |
-
GitPython-3.1.43.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
8 |
-
GitPython-3.1.43.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
|
9 |
-
git/__init__.py,sha256=w6fnS0QmwTfEFUSL6rfnpP0lUId2goSguZFOvVX3N3U,8899
|
10 |
-
git/__pycache__/__init__.cpython-311.pyc,,
|
11 |
-
git/__pycache__/cmd.cpython-311.pyc,,
|
12 |
-
git/__pycache__/compat.cpython-311.pyc,,
|
13 |
-
git/__pycache__/config.cpython-311.pyc,,
|
14 |
-
git/__pycache__/db.cpython-311.pyc,,
|
15 |
-
git/__pycache__/diff.cpython-311.pyc,,
|
16 |
-
git/__pycache__/exc.cpython-311.pyc,,
|
17 |
-
git/__pycache__/remote.cpython-311.pyc,,
|
18 |
-
git/__pycache__/types.cpython-311.pyc,,
|
19 |
-
git/__pycache__/util.cpython-311.pyc,,
|
20 |
-
git/cmd.py,sha256=qd-gIHSk4mfsYjd9YA08cPyO8TMxaibTXAbFnHK71uc,67659
|
21 |
-
git/compat.py,sha256=y1E6y6O2q5r8clSlr8ZNmuIWG9nmHuehQEsVsmBffs8,4526
|
22 |
-
git/config.py,sha256=Ald8Xc-G9Shcgx3QCISyXTkL4a6nbc3qll-xUw4YdyY,34924
|
23 |
-
git/db.py,sha256=vIW9uWSbqu99zbuU2ZDmOhVOv1UPTmxrnqiCtRHCfjE,2368
|
24 |
-
git/diff.py,sha256=IE5aeHL7aP9yxBluYj06IX8nZjoJ_TOM3gG31-Evf_8,27058
|
25 |
-
git/exc.py,sha256=Gc7g1pHpn8OmTse30NHmJVsBJ2CYH8LxaR8y8UA3lIM,7119
|
26 |
-
git/index/__init__.py,sha256=i-Nqb8Lufp9aFbmxpQBORmmQnjEVVM1Pn58fsQkyGgQ,406
|
27 |
-
git/index/__pycache__/__init__.cpython-311.pyc,,
|
28 |
-
git/index/__pycache__/base.cpython-311.pyc,,
|
29 |
-
git/index/__pycache__/fun.cpython-311.pyc,,
|
30 |
-
git/index/__pycache__/typ.cpython-311.pyc,,
|
31 |
-
git/index/__pycache__/util.cpython-311.pyc,,
|
32 |
-
git/index/base.py,sha256=A4q4cN_Ifxi8CsAR-7h4KsQ2d3JazBNFZ1ltbAKttgs,60734
|
33 |
-
git/index/fun.py,sha256=37cA3DBC9vpAnSVu5TGA072SnoF5XZOkOukExwlejHs,16736
|
34 |
-
git/index/typ.py,sha256=uuKNwitUw83FhVaLSwo4pY7PHDQudtZTLJrLGym4jcI,6570
|
35 |
-
git/index/util.py,sha256=fULi7GPG-MvprKrRCD5c15GNdzku_1E38We0d97WB3A,3659
|
36 |
-
git/objects/__init__.py,sha256=O6ZL_olX7e5-8iIbKviRPkVSJxN37WA-EC0q9d48U5Y,637
|
37 |
-
git/objects/__pycache__/__init__.cpython-311.pyc,,
|
38 |
-
git/objects/__pycache__/base.cpython-311.pyc,,
|
39 |
-
git/objects/__pycache__/blob.cpython-311.pyc,,
|
40 |
-
git/objects/__pycache__/commit.cpython-311.pyc,,
|
41 |
-
git/objects/__pycache__/fun.cpython-311.pyc,,
|
42 |
-
git/objects/__pycache__/tag.cpython-311.pyc,,
|
43 |
-
git/objects/__pycache__/tree.cpython-311.pyc,,
|
44 |
-
git/objects/__pycache__/util.cpython-311.pyc,,
|
45 |
-
git/objects/base.py,sha256=0dqNkSRVH0mk0-7ZKIkGBK7iNYrzLTVxwQFUd6CagsE,10277
|
46 |
-
git/objects/blob.py,sha256=zwwq0KfOMYeP5J2tW5CQatoLyeqFRlfkxP1Vwx1h07s,1215
|
47 |
-
git/objects/commit.py,sha256=vLZNl1I9zp17Rpge7J66CvsryirEs90jyPTQzoP0JJs,30208
|
48 |
-
git/objects/fun.py,sha256=B4jCqhAjm6Hl79GK58FPzW1H9K6Wc7Tx0rssyWmAcEE,8935
|
49 |
-
git/objects/submodule/__init__.py,sha256=6xySp767LVz3UylWgUalntS_nGXRuVzXxDuFAv_Wc2c,303
|
50 |
-
git/objects/submodule/__pycache__/__init__.cpython-311.pyc,,
|
51 |
-
git/objects/submodule/__pycache__/base.cpython-311.pyc,,
|
52 |
-
git/objects/submodule/__pycache__/root.cpython-311.pyc,,
|
53 |
-
git/objects/submodule/__pycache__/util.cpython-311.pyc,,
|
54 |
-
git/objects/submodule/base.py,sha256=MQ-2xV8JznGwy2hLQv1aeQNgAkhBhgc5tdtClFL3DmE,63901
|
55 |
-
git/objects/submodule/root.py,sha256=5eTtYNHasqdPq6q0oDCPr7IaO6uAHL3b4DxMoiO2LhE,20246
|
56 |
-
git/objects/submodule/util.py,sha256=sQqAYaiSJdFkZa9NlAuK_wTsMNiS-kkQnQjvIoJtc_o,3509
|
57 |
-
git/objects/tag.py,sha256=gAx8i-DEwy_Z3R2zLkvetYRV8A56BCcTr3iLuTUTfEM,4467
|
58 |
-
git/objects/tree.py,sha256=jJH888SHiP4dGzE-ra1yenQOyya_0C_MkHr06c1gHpM,13849
|
59 |
-
git/objects/util.py,sha256=Ml2eqZPKO4y9Hc2vWbXJgpsK3nkN3KGMzbn8AlzLyYQ,23834
|
60 |
-
git/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
61 |
-
git/refs/__init__.py,sha256=DWlJNnsx-4jM_E-VycbP-FZUdn6iWhjnH_uZ_pZXBro,509
|
62 |
-
git/refs/__pycache__/__init__.cpython-311.pyc,,
|
63 |
-
git/refs/__pycache__/head.cpython-311.pyc,,
|
64 |
-
git/refs/__pycache__/log.cpython-311.pyc,,
|
65 |
-
git/refs/__pycache__/reference.cpython-311.pyc,,
|
66 |
-
git/refs/__pycache__/remote.cpython-311.pyc,,
|
67 |
-
git/refs/__pycache__/symbolic.cpython-311.pyc,,
|
68 |
-
git/refs/__pycache__/tag.cpython-311.pyc,,
|
69 |
-
git/refs/head.py,sha256=GAZpD5EfqSciDXPtgjHY8ZbBixKExJRhojUB-HrrJPg,10491
|
70 |
-
git/refs/log.py,sha256=kXiuAgTo1DIuM_BfbDUk9gQ0YO-mutIMVdHv1_ES90o,12493
|
71 |
-
git/refs/reference.py,sha256=l6mhF4YLSEwtjz6b9PpOQH-fkng7EYWMaJhkjn-2jXA,5630
|
72 |
-
git/refs/remote.py,sha256=WwqV9T7BbYf3F_WZNUQivu9xktIIKGklCjDpwQrhD-A,2806
|
73 |
-
git/refs/symbolic.py,sha256=c8zOwaqzcg-J-rGrpuWdvh8zwMvSUqAHghd4vJoYG_s,34552
|
74 |
-
git/refs/tag.py,sha256=kgzV2vhpL4FD2TqHb0BJuMRAHgAvJF-TcoyWlaB-djQ,5010
|
75 |
-
git/remote.py,sha256=IHQ3BvXgoIN1EvHlyH3vrSaQoDkLOE6nooSC0w183sU,46561
|
76 |
-
git/repo/__init__.py,sha256=CILSVH36fX_WxVFSjD9o1WF5LgsNedPiJvSngKZqfVU,210
|
77 |
-
git/repo/__pycache__/__init__.cpython-311.pyc,,
|
78 |
-
git/repo/__pycache__/base.cpython-311.pyc,,
|
79 |
-
git/repo/__pycache__/fun.cpython-311.pyc,,
|
80 |
-
git/repo/base.py,sha256=mitfJ8u99CsMpDd7_VRyx-SF8omu2tpf3lqzSaQkKoQ,59353
|
81 |
-
git/repo/fun.py,sha256=tEsClpmbOrKMSNIdncOB_6JdikrL1-AfkOFd7xMpD8k,13582
|
82 |
-
git/types.py,sha256=xCwpp2Y01lhS0MapHhj04m0P_x34kwSD1Gsou_ZPWj8,10251
|
83 |
-
git/util.py,sha256=1E883mnPAFLyFk7ivwnEremsp-uJOTc3ks_QypyLung,43651
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/REQUESTED
DELETED
File without changes
|
ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/WHEEL
DELETED
@@ -1,5 +0,0 @@
|
|
1 |
-
Wheel-Version: 1.0
|
2 |
-
Generator: bdist_wheel (0.43.0)
|
3 |
-
Root-Is-Purelib: true
|
4 |
-
Tag: py3-none-any
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/GitPython-3.1.43.dist-info/top_level.txt
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
git
|
|
|
|
ILYA/Lib/site-packages/_distutils_hack/__pycache__/__init__.cpython-311.pyc
CHANGED
Binary files a/ILYA/Lib/site-packages/_distutils_hack/__pycache__/__init__.cpython-311.pyc and b/ILYA/Lib/site-packages/_distutils_hack/__pycache__/__init__.cpython-311.pyc differ
|
|
ILYA/Lib/site-packages/_distutils_hack/__pycache__/override.cpython-311.pyc
CHANGED
Binary files a/ILYA/Lib/site-packages/_distutils_hack/__pycache__/override.cpython-311.pyc and b/ILYA/Lib/site-packages/_distutils_hack/__pycache__/override.cpython-311.pyc differ
|
|
ILYA/Lib/site-packages/git/__init__.py
DELETED
@@ -1,300 +0,0 @@
|
|
1 |
-
# Copyright (C) 2008, 2009 Michael Trier ([email protected]) and contributors
|
2 |
-
#
|
3 |
-
# This module is part of GitPython and is released under the
|
4 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
5 |
-
|
6 |
-
# @PydevCodeAnalysisIgnore
|
7 |
-
|
8 |
-
__all__ = [
|
9 |
-
"Actor",
|
10 |
-
"AmbiguousObjectName",
|
11 |
-
"BadName",
|
12 |
-
"BadObject",
|
13 |
-
"BadObjectType",
|
14 |
-
"BaseIndexEntry",
|
15 |
-
"Blob",
|
16 |
-
"BlobFilter",
|
17 |
-
"BlockingLockFile",
|
18 |
-
"CacheError",
|
19 |
-
"CheckoutError",
|
20 |
-
"CommandError",
|
21 |
-
"Commit",
|
22 |
-
"Diff",
|
23 |
-
"DiffConstants",
|
24 |
-
"DiffIndex",
|
25 |
-
"Diffable",
|
26 |
-
"FetchInfo",
|
27 |
-
"Git",
|
28 |
-
"GitCmdObjectDB",
|
29 |
-
"GitCommandError",
|
30 |
-
"GitCommandNotFound",
|
31 |
-
"GitConfigParser",
|
32 |
-
"GitDB",
|
33 |
-
"GitError",
|
34 |
-
"HEAD",
|
35 |
-
"Head",
|
36 |
-
"HookExecutionError",
|
37 |
-
"INDEX",
|
38 |
-
"IndexEntry",
|
39 |
-
"IndexFile",
|
40 |
-
"IndexObject",
|
41 |
-
"InvalidDBRoot",
|
42 |
-
"InvalidGitRepositoryError",
|
43 |
-
"List", # Deprecated - import this from `typing` instead.
|
44 |
-
"LockFile",
|
45 |
-
"NULL_TREE",
|
46 |
-
"NoSuchPathError",
|
47 |
-
"ODBError",
|
48 |
-
"Object",
|
49 |
-
"Optional", # Deprecated - import this from `typing` instead.
|
50 |
-
"ParseError",
|
51 |
-
"PathLike",
|
52 |
-
"PushInfo",
|
53 |
-
"RefLog",
|
54 |
-
"RefLogEntry",
|
55 |
-
"Reference",
|
56 |
-
"Remote",
|
57 |
-
"RemoteProgress",
|
58 |
-
"RemoteReference",
|
59 |
-
"Repo",
|
60 |
-
"RepositoryDirtyError",
|
61 |
-
"RootModule",
|
62 |
-
"RootUpdateProgress",
|
63 |
-
"Sequence", # Deprecated - import from `typing`, or `collections.abc` in 3.9+.
|
64 |
-
"StageType",
|
65 |
-
"Stats",
|
66 |
-
"Submodule",
|
67 |
-
"SymbolicReference",
|
68 |
-
"TYPE_CHECKING", # Deprecated - import this from `typing` instead.
|
69 |
-
"Tag",
|
70 |
-
"TagObject",
|
71 |
-
"TagReference",
|
72 |
-
"Tree",
|
73 |
-
"TreeModifier",
|
74 |
-
"Tuple", # Deprecated - import this from `typing` instead.
|
75 |
-
"Union", # Deprecated - import this from `typing` instead.
|
76 |
-
"UnmergedEntriesError",
|
77 |
-
"UnsafeOptionError",
|
78 |
-
"UnsafeProtocolError",
|
79 |
-
"UnsupportedOperation",
|
80 |
-
"UpdateProgress",
|
81 |
-
"WorkTreeRepositoryUnsupported",
|
82 |
-
"refresh",
|
83 |
-
"remove_password_if_present",
|
84 |
-
"rmtree",
|
85 |
-
"safe_decode",
|
86 |
-
"to_hex_sha",
|
87 |
-
]
|
88 |
-
|
89 |
-
__version__ = '3.1.43'
|
90 |
-
|
91 |
-
from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Tuple, Union
|
92 |
-
|
93 |
-
if TYPE_CHECKING:
|
94 |
-
from types import ModuleType
|
95 |
-
|
96 |
-
import warnings
|
97 |
-
|
98 |
-
from gitdb.util import to_hex_sha
|
99 |
-
|
100 |
-
from git.exc import (
|
101 |
-
AmbiguousObjectName,
|
102 |
-
BadName,
|
103 |
-
BadObject,
|
104 |
-
BadObjectType,
|
105 |
-
CacheError,
|
106 |
-
CheckoutError,
|
107 |
-
CommandError,
|
108 |
-
GitCommandError,
|
109 |
-
GitCommandNotFound,
|
110 |
-
GitError,
|
111 |
-
HookExecutionError,
|
112 |
-
InvalidDBRoot,
|
113 |
-
InvalidGitRepositoryError,
|
114 |
-
NoSuchPathError,
|
115 |
-
ODBError,
|
116 |
-
ParseError,
|
117 |
-
RepositoryDirtyError,
|
118 |
-
UnmergedEntriesError,
|
119 |
-
UnsafeOptionError,
|
120 |
-
UnsafeProtocolError,
|
121 |
-
UnsupportedOperation,
|
122 |
-
WorkTreeRepositoryUnsupported,
|
123 |
-
)
|
124 |
-
from git.types import PathLike
|
125 |
-
|
126 |
-
try:
|
127 |
-
from git.compat import safe_decode # @NoMove
|
128 |
-
from git.config import GitConfigParser # @NoMove
|
129 |
-
from git.objects import ( # @NoMove
|
130 |
-
Blob,
|
131 |
-
Commit,
|
132 |
-
IndexObject,
|
133 |
-
Object,
|
134 |
-
RootModule,
|
135 |
-
RootUpdateProgress,
|
136 |
-
Submodule,
|
137 |
-
TagObject,
|
138 |
-
Tree,
|
139 |
-
TreeModifier,
|
140 |
-
UpdateProgress,
|
141 |
-
)
|
142 |
-
from git.refs import ( # @NoMove
|
143 |
-
HEAD,
|
144 |
-
Head,
|
145 |
-
RefLog,
|
146 |
-
RefLogEntry,
|
147 |
-
Reference,
|
148 |
-
RemoteReference,
|
149 |
-
SymbolicReference,
|
150 |
-
Tag,
|
151 |
-
TagReference,
|
152 |
-
)
|
153 |
-
from git.diff import ( # @NoMove
|
154 |
-
INDEX,
|
155 |
-
NULL_TREE,
|
156 |
-
Diff,
|
157 |
-
DiffConstants,
|
158 |
-
DiffIndex,
|
159 |
-
Diffable,
|
160 |
-
)
|
161 |
-
from git.db import GitCmdObjectDB, GitDB # @NoMove
|
162 |
-
from git.cmd import Git # @NoMove
|
163 |
-
from git.repo import Repo # @NoMove
|
164 |
-
from git.remote import FetchInfo, PushInfo, Remote, RemoteProgress # @NoMove
|
165 |
-
from git.index import ( # @NoMove
|
166 |
-
BaseIndexEntry,
|
167 |
-
BlobFilter,
|
168 |
-
CheckoutError,
|
169 |
-
IndexEntry,
|
170 |
-
IndexFile,
|
171 |
-
StageType,
|
172 |
-
# NOTE: This tells type checkers what util resolves to. We delete it, and it is
|
173 |
-
# really resolved by __getattr__, which warns. See below on what to use instead.
|
174 |
-
util,
|
175 |
-
)
|
176 |
-
from git.util import ( # @NoMove
|
177 |
-
Actor,
|
178 |
-
BlockingLockFile,
|
179 |
-
LockFile,
|
180 |
-
Stats,
|
181 |
-
remove_password_if_present,
|
182 |
-
rmtree,
|
183 |
-
)
|
184 |
-
except GitError as _exc:
|
185 |
-
raise ImportError("%s: %s" % (_exc.__class__.__name__, _exc)) from _exc
|
186 |
-
|
187 |
-
|
188 |
-
def _warned_import(message: str, fullname: str) -> "ModuleType":
|
189 |
-
import importlib
|
190 |
-
|
191 |
-
warnings.warn(message, DeprecationWarning, stacklevel=3)
|
192 |
-
return importlib.import_module(fullname)
|
193 |
-
|
194 |
-
|
195 |
-
def _getattr(name: str) -> Any:
|
196 |
-
# TODO: If __version__ is made dynamic and lazily fetched, put that case right here.
|
197 |
-
|
198 |
-
if name == "util":
|
199 |
-
return _warned_import(
|
200 |
-
"The expression `git.util` and the import `from git import util` actually "
|
201 |
-
"reference git.index.util, and not the git.util module accessed in "
|
202 |
-
'`from git.util import XYZ` or `sys.modules["git.util"]`. This potentially '
|
203 |
-
"confusing behavior is currently preserved for compatibility, but may be "
|
204 |
-
"changed in the future and should not be relied on.",
|
205 |
-
fullname="git.index.util",
|
206 |
-
)
|
207 |
-
|
208 |
-
for names, prefix in (
|
209 |
-
({"head", "log", "reference", "symbolic", "tag"}, "git.refs"),
|
210 |
-
({"base", "fun", "typ"}, "git.index"),
|
211 |
-
):
|
212 |
-
if name not in names:
|
213 |
-
continue
|
214 |
-
|
215 |
-
fullname = f"{prefix}.{name}"
|
216 |
-
|
217 |
-
return _warned_import(
|
218 |
-
f"{__name__}.{name} is a private alias of {fullname} and subject to "
|
219 |
-
f"immediate removal. Use {fullname} instead.",
|
220 |
-
fullname=fullname,
|
221 |
-
)
|
222 |
-
|
223 |
-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
224 |
-
|
225 |
-
|
226 |
-
if not TYPE_CHECKING:
|
227 |
-
# NOTE: The expression `git.util` gives git.index.util and `from git import util`
|
228 |
-
# imports git.index.util, NOT git.util. It may not be feasible to change this until
|
229 |
-
# the next major version, to avoid breaking code inadvertently relying on it.
|
230 |
-
#
|
231 |
-
# - If git.index.util *is* what you want, use (or import from) that, to avoid
|
232 |
-
# confusion.
|
233 |
-
#
|
234 |
-
# - To use the "real" git.util module, write `from git.util import ...`, or if
|
235 |
-
# necessary access it as `sys.modules["git.util"]`.
|
236 |
-
#
|
237 |
-
# Note also that `import git.util` technically imports the "real" git.util... but
|
238 |
-
# the *expression* `git.util` after doing so is still git.index.util!
|
239 |
-
#
|
240 |
-
# (This situation differs from that of other indirect-submodule imports that are
|
241 |
-
# unambiguously non-public and subject to immediate removal. Here, the public
|
242 |
-
# git.util module, though different, makes less discoverable that the expression
|
243 |
-
# `git.util` refers to a non-public attribute of the git module.)
|
244 |
-
#
|
245 |
-
# This had originally come about by a wildcard import. Now that all intended imports
|
246 |
-
# are explicit, the intuitive but potentially incompatible binding occurs due to the
|
247 |
-
# usual rules for Python submodule bindings. So for now we replace that binding with
|
248 |
-
# git.index.util, delete that, and let __getattr__ handle it and issue a warning.
|
249 |
-
#
|
250 |
-
# For the same runtime behavior, it would be enough to forgo importing util, and
|
251 |
-
# delete util as created naturally; __getattr__ would behave the same. But type
|
252 |
-
# checkers would not know what util refers to when accessed as an attribute of git.
|
253 |
-
del util
|
254 |
-
|
255 |
-
# This is "hidden" to preserve static checking for undefined/misspelled attributes.
|
256 |
-
__getattr__ = _getattr
|
257 |
-
|
258 |
-
# { Initialize git executable path
|
259 |
-
|
260 |
-
GIT_OK = None
|
261 |
-
|
262 |
-
|
263 |
-
def refresh(path: Optional[PathLike] = None) -> None:
|
264 |
-
"""Convenience method for setting the git executable path.
|
265 |
-
|
266 |
-
:param path:
|
267 |
-
Optional path to the Git executable. If not absolute, it is resolved
|
268 |
-
immediately, relative to the current directory.
|
269 |
-
|
270 |
-
:note:
|
271 |
-
The `path` parameter is usually omitted and cannot be used to specify a custom
|
272 |
-
command whose location is looked up in a path search on each call. See
|
273 |
-
:meth:`Git.refresh <git.cmd.Git.refresh>` for details on how to achieve this.
|
274 |
-
|
275 |
-
:note:
|
276 |
-
This calls :meth:`Git.refresh <git.cmd.Git.refresh>` and sets other global
|
277 |
-
configuration according to the effect of doing so. As such, this function should
|
278 |
-
usually be used instead of using :meth:`Git.refresh <git.cmd.Git.refresh>` or
|
279 |
-
:meth:`FetchInfo.refresh <git.remote.FetchInfo.refresh>` directly.
|
280 |
-
|
281 |
-
:note:
|
282 |
-
This function is called automatically, with no arguments, at import time.
|
283 |
-
"""
|
284 |
-
global GIT_OK
|
285 |
-
GIT_OK = False
|
286 |
-
|
287 |
-
if not Git.refresh(path=path):
|
288 |
-
return
|
289 |
-
if not FetchInfo.refresh(): # noqa: F405
|
290 |
-
return # type: ignore[unreachable]
|
291 |
-
|
292 |
-
GIT_OK = True
|
293 |
-
|
294 |
-
|
295 |
-
try:
|
296 |
-
refresh()
|
297 |
-
except Exception as _exc:
|
298 |
-
raise ImportError("Failed to initialize: {0}".format(_exc)) from _exc
|
299 |
-
|
300 |
-
# } END initialize git executable path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/__pycache__/__init__.cpython-311.pyc
DELETED
Binary file (7.3 kB)
|
|
ILYA/Lib/site-packages/git/__pycache__/cmd.cpython-311.pyc
DELETED
Binary file (68.9 kB)
|
|
ILYA/Lib/site-packages/git/__pycache__/compat.cpython-311.pyc
DELETED
Binary file (5.18 kB)
|
|
ILYA/Lib/site-packages/git/__pycache__/config.cpython-311.pyc
DELETED
Binary file (45.9 kB)
|
|
ILYA/Lib/site-packages/git/__pycache__/db.cpython-311.pyc
DELETED
Binary file (3.76 kB)
|
|
ILYA/Lib/site-packages/git/__pycache__/diff.cpython-311.pyc
DELETED
Binary file (28.3 kB)
|
|
ILYA/Lib/site-packages/git/__pycache__/exc.cpython-311.pyc
DELETED
Binary file (11.8 kB)
|
|
ILYA/Lib/site-packages/git/__pycache__/remote.cpython-311.pyc
DELETED
Binary file (52.9 kB)
|
|
ILYA/Lib/site-packages/git/__pycache__/types.cpython-311.pyc
DELETED
Binary file (6.61 kB)
|
|
ILYA/Lib/site-packages/git/__pycache__/util.cpython-311.pyc
DELETED
Binary file (60.4 kB)
|
|
ILYA/Lib/site-packages/git/cmd.py
DELETED
@@ -1,1723 +0,0 @@
|
|
1 |
-
# Copyright (C) 2008, 2009 Michael Trier ([email protected]) and contributors
|
2 |
-
#
|
3 |
-
# This module is part of GitPython and is released under the
|
4 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
5 |
-
|
6 |
-
from __future__ import annotations
|
7 |
-
|
8 |
-
__all__ = ["GitMeta", "Git"]
|
9 |
-
|
10 |
-
import contextlib
|
11 |
-
import io
|
12 |
-
import itertools
|
13 |
-
import logging
|
14 |
-
import os
|
15 |
-
import re
|
16 |
-
import signal
|
17 |
-
import subprocess
|
18 |
-
from subprocess import DEVNULL, PIPE, Popen
|
19 |
-
import sys
|
20 |
-
from textwrap import dedent
|
21 |
-
import threading
|
22 |
-
import warnings
|
23 |
-
|
24 |
-
from git.compat import defenc, force_bytes, safe_decode
|
25 |
-
from git.exc import (
|
26 |
-
CommandError,
|
27 |
-
GitCommandError,
|
28 |
-
GitCommandNotFound,
|
29 |
-
UnsafeOptionError,
|
30 |
-
UnsafeProtocolError,
|
31 |
-
)
|
32 |
-
from git.util import (
|
33 |
-
cygpath,
|
34 |
-
expand_path,
|
35 |
-
is_cygwin_git,
|
36 |
-
patch_env,
|
37 |
-
remove_password_if_present,
|
38 |
-
stream_copy,
|
39 |
-
)
|
40 |
-
|
41 |
-
# typing ---------------------------------------------------------------------------
|
42 |
-
|
43 |
-
from typing import (
|
44 |
-
Any,
|
45 |
-
AnyStr,
|
46 |
-
BinaryIO,
|
47 |
-
Callable,
|
48 |
-
Dict,
|
49 |
-
IO,
|
50 |
-
Iterator,
|
51 |
-
List,
|
52 |
-
Mapping,
|
53 |
-
Optional,
|
54 |
-
Sequence,
|
55 |
-
TYPE_CHECKING,
|
56 |
-
TextIO,
|
57 |
-
Tuple,
|
58 |
-
Union,
|
59 |
-
cast,
|
60 |
-
overload,
|
61 |
-
)
|
62 |
-
|
63 |
-
from git.types import Literal, PathLike, TBD
|
64 |
-
|
65 |
-
if TYPE_CHECKING:
|
66 |
-
from git.diff import DiffIndex
|
67 |
-
from git.repo.base import Repo
|
68 |
-
|
69 |
-
# ---------------------------------------------------------------------------------
|
70 |
-
|
71 |
-
execute_kwargs = {
|
72 |
-
"istream",
|
73 |
-
"with_extended_output",
|
74 |
-
"with_exceptions",
|
75 |
-
"as_process",
|
76 |
-
"output_stream",
|
77 |
-
"stdout_as_string",
|
78 |
-
"kill_after_timeout",
|
79 |
-
"with_stdout",
|
80 |
-
"universal_newlines",
|
81 |
-
"shell",
|
82 |
-
"env",
|
83 |
-
"max_chunk_size",
|
84 |
-
"strip_newline_in_stdout",
|
85 |
-
}
|
86 |
-
|
87 |
-
_logger = logging.getLogger(__name__)
|
88 |
-
|
89 |
-
|
90 |
-
# ==============================================================================
|
91 |
-
## @name Utilities
|
92 |
-
# ------------------------------------------------------------------------------
|
93 |
-
# Documentation
|
94 |
-
## @{
|
95 |
-
|
96 |
-
|
97 |
-
def handle_process_output(
|
98 |
-
process: "Git.AutoInterrupt" | Popen,
|
99 |
-
stdout_handler: Union[
|
100 |
-
None,
|
101 |
-
Callable[[AnyStr], None],
|
102 |
-
Callable[[List[AnyStr]], None],
|
103 |
-
Callable[[bytes, "Repo", "DiffIndex"], None],
|
104 |
-
],
|
105 |
-
stderr_handler: Union[None, Callable[[AnyStr], None], Callable[[List[AnyStr]], None]],
|
106 |
-
finalizer: Union[None, Callable[[Union[Popen, "Git.AutoInterrupt"]], None]] = None,
|
107 |
-
decode_streams: bool = True,
|
108 |
-
kill_after_timeout: Union[None, float] = None,
|
109 |
-
) -> None:
|
110 |
-
R"""Register for notifications to learn that process output is ready to read, and
|
111 |
-
dispatch lines to the respective line handlers.
|
112 |
-
|
113 |
-
This function returns once the finalizer returns.
|
114 |
-
|
115 |
-
:param process:
|
116 |
-
:class:`subprocess.Popen` instance.
|
117 |
-
|
118 |
-
:param stdout_handler:
|
119 |
-
f(stdout_line_string), or ``None``.
|
120 |
-
|
121 |
-
:param stderr_handler:
|
122 |
-
f(stderr_line_string), or ``None``.
|
123 |
-
|
124 |
-
:param finalizer:
|
125 |
-
f(proc) - wait for proc to finish.
|
126 |
-
|
127 |
-
:param decode_streams:
|
128 |
-
Assume stdout/stderr streams are binary and decode them before pushing their
|
129 |
-
contents to handlers.
|
130 |
-
|
131 |
-
This defaults to ``True``. Set it to ``False`` if:
|
132 |
-
|
133 |
-
- ``universal_newlines == True``, as then streams are in text mode, or
|
134 |
-
- decoding must happen later, such as for :class:`~git.diff.Diff`\s.
|
135 |
-
|
136 |
-
:param kill_after_timeout:
|
137 |
-
:class:`float` or ``None``, Default = ``None``
|
138 |
-
|
139 |
-
To specify a timeout in seconds for the git command, after which the process
|
140 |
-
should be killed.
|
141 |
-
"""
|
142 |
-
|
143 |
-
# Use 2 "pump" threads and wait for both to finish.
|
144 |
-
def pump_stream(
|
145 |
-
cmdline: List[str],
|
146 |
-
name: str,
|
147 |
-
stream: Union[BinaryIO, TextIO],
|
148 |
-
is_decode: bool,
|
149 |
-
handler: Union[None, Callable[[Union[bytes, str]], None]],
|
150 |
-
) -> None:
|
151 |
-
try:
|
152 |
-
for line in stream:
|
153 |
-
if handler:
|
154 |
-
if is_decode:
|
155 |
-
assert isinstance(line, bytes)
|
156 |
-
line_str = line.decode(defenc)
|
157 |
-
handler(line_str)
|
158 |
-
else:
|
159 |
-
handler(line)
|
160 |
-
|
161 |
-
except Exception as ex:
|
162 |
-
_logger.error(f"Pumping {name!r} of cmd({remove_password_if_present(cmdline)}) failed due to: {ex!r}")
|
163 |
-
if "I/O operation on closed file" not in str(ex):
|
164 |
-
# Only reraise if the error was not due to the stream closing.
|
165 |
-
raise CommandError([f"<{name}-pump>"] + remove_password_if_present(cmdline), ex) from ex
|
166 |
-
finally:
|
167 |
-
stream.close()
|
168 |
-
|
169 |
-
if hasattr(process, "proc"):
|
170 |
-
process = cast("Git.AutoInterrupt", process)
|
171 |
-
cmdline: str | Tuple[str, ...] | List[str] = getattr(process.proc, "args", "")
|
172 |
-
p_stdout = process.proc.stdout if process.proc else None
|
173 |
-
p_stderr = process.proc.stderr if process.proc else None
|
174 |
-
else:
|
175 |
-
process = cast(Popen, process) # type: ignore[redundant-cast]
|
176 |
-
cmdline = getattr(process, "args", "")
|
177 |
-
p_stdout = process.stdout
|
178 |
-
p_stderr = process.stderr
|
179 |
-
|
180 |
-
if not isinstance(cmdline, (tuple, list)):
|
181 |
-
cmdline = cmdline.split()
|
182 |
-
|
183 |
-
pumps: List[Tuple[str, IO, Callable[..., None] | None]] = []
|
184 |
-
if p_stdout:
|
185 |
-
pumps.append(("stdout", p_stdout, stdout_handler))
|
186 |
-
if p_stderr:
|
187 |
-
pumps.append(("stderr", p_stderr, stderr_handler))
|
188 |
-
|
189 |
-
threads: List[threading.Thread] = []
|
190 |
-
|
191 |
-
for name, stream, handler in pumps:
|
192 |
-
t = threading.Thread(target=pump_stream, args=(cmdline, name, stream, decode_streams, handler))
|
193 |
-
t.daemon = True
|
194 |
-
t.start()
|
195 |
-
threads.append(t)
|
196 |
-
|
197 |
-
# FIXME: Why join? Will block if stdin needs feeding...
|
198 |
-
for t in threads:
|
199 |
-
t.join(timeout=kill_after_timeout)
|
200 |
-
if t.is_alive():
|
201 |
-
if isinstance(process, Git.AutoInterrupt):
|
202 |
-
process._terminate()
|
203 |
-
else: # Don't want to deal with the other case.
|
204 |
-
raise RuntimeError(
|
205 |
-
"Thread join() timed out in cmd.handle_process_output()."
|
206 |
-
f" kill_after_timeout={kill_after_timeout} seconds"
|
207 |
-
)
|
208 |
-
if stderr_handler:
|
209 |
-
error_str: Union[str, bytes] = (
|
210 |
-
"error: process killed because it timed out." f" kill_after_timeout={kill_after_timeout} seconds"
|
211 |
-
)
|
212 |
-
if not decode_streams and isinstance(p_stderr, BinaryIO):
|
213 |
-
# Assume stderr_handler needs binary input.
|
214 |
-
error_str = cast(str, error_str)
|
215 |
-
error_str = error_str.encode()
|
216 |
-
# We ignore typing on the next line because mypy does not like the way
|
217 |
-
# we inferred that stderr takes str or bytes.
|
218 |
-
stderr_handler(error_str) # type: ignore[arg-type]
|
219 |
-
|
220 |
-
if finalizer:
|
221 |
-
finalizer(process)
|
222 |
-
|
223 |
-
|
224 |
-
safer_popen: Callable[..., Popen]
|
225 |
-
|
226 |
-
if sys.platform == "win32":
|
227 |
-
|
228 |
-
def _safer_popen_windows(
|
229 |
-
command: Union[str, Sequence[Any]],
|
230 |
-
*,
|
231 |
-
shell: bool = False,
|
232 |
-
env: Optional[Mapping[str, str]] = None,
|
233 |
-
**kwargs: Any,
|
234 |
-
) -> Popen:
|
235 |
-
"""Call :class:`subprocess.Popen` on Windows but don't include a CWD in the
|
236 |
-
search.
|
237 |
-
|
238 |
-
This avoids an untrusted search path condition where a file like ``git.exe`` in
|
239 |
-
a malicious repository would be run when GitPython operates on the repository.
|
240 |
-
The process using GitPython may have an untrusted repository's working tree as
|
241 |
-
its current working directory. Some operations may temporarily change to that
|
242 |
-
directory before running a subprocess. In addition, while by default GitPython
|
243 |
-
does not run external commands with a shell, it can be made to do so, in which
|
244 |
-
case the CWD of the subprocess, which GitPython usually sets to a repository
|
245 |
-
working tree, can itself be searched automatically by the shell. This wrapper
|
246 |
-
covers all those cases.
|
247 |
-
|
248 |
-
:note:
|
249 |
-
This currently works by setting the
|
250 |
-
:envvar:`NoDefaultCurrentDirectoryInExePath` environment variable during
|
251 |
-
subprocess creation. It also takes care of passing Windows-specific process
|
252 |
-
creation flags, but that is unrelated to path search.
|
253 |
-
|
254 |
-
:note:
|
255 |
-
The current implementation contains a race condition on :attr:`os.environ`.
|
256 |
-
GitPython isn't thread-safe, but a program using it on one thread should
|
257 |
-
ideally be able to mutate :attr:`os.environ` on another, without
|
258 |
-
unpredictable results. See comments in:
|
259 |
-
https://github.com/gitpython-developers/GitPython/pull/1650
|
260 |
-
"""
|
261 |
-
# CREATE_NEW_PROCESS_GROUP is needed for some ways of killing it afterwards.
|
262 |
-
# https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
|
263 |
-
# https://docs.python.org/3/library/subprocess.html#subprocess.CREATE_NEW_PROCESS_GROUP
|
264 |
-
creationflags = subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP
|
265 |
-
|
266 |
-
# When using a shell, the shell is the direct subprocess, so the variable must
|
267 |
-
# be set in its environment, to affect its search behavior.
|
268 |
-
if shell:
|
269 |
-
# The original may be immutable, or the caller may reuse it. Mutate a copy.
|
270 |
-
env = {} if env is None else dict(env)
|
271 |
-
env["NoDefaultCurrentDirectoryInExePath"] = "1" # The "1" can be an value.
|
272 |
-
|
273 |
-
# When not using a shell, the current process does the search in a
|
274 |
-
# CreateProcessW API call, so the variable must be set in our environment. With
|
275 |
-
# a shell, that's unnecessary if https://github.com/python/cpython/issues/101283
|
276 |
-
# is patched. In Python versions where it is unpatched, and in the rare case the
|
277 |
-
# ComSpec environment variable is unset, the search for the shell itself is
|
278 |
-
# unsafe. Setting NoDefaultCurrentDirectoryInExePath in all cases, as done here,
|
279 |
-
# is simpler and protects against that. (As above, the "1" can be any value.)
|
280 |
-
with patch_env("NoDefaultCurrentDirectoryInExePath", "1"):
|
281 |
-
return Popen(
|
282 |
-
command,
|
283 |
-
shell=shell,
|
284 |
-
env=env,
|
285 |
-
creationflags=creationflags,
|
286 |
-
**kwargs,
|
287 |
-
)
|
288 |
-
|
289 |
-
safer_popen = _safer_popen_windows
|
290 |
-
else:
|
291 |
-
safer_popen = Popen
|
292 |
-
|
293 |
-
|
294 |
-
def dashify(string: str) -> str:
|
295 |
-
return string.replace("_", "-")
|
296 |
-
|
297 |
-
|
298 |
-
def slots_to_dict(self: "Git", exclude: Sequence[str] = ()) -> Dict[str, Any]:
|
299 |
-
return {s: getattr(self, s) for s in self.__slots__ if s not in exclude}
|
300 |
-
|
301 |
-
|
302 |
-
def dict_to_slots_and__excluded_are_none(self: object, d: Mapping[str, Any], excluded: Sequence[str] = ()) -> None:
|
303 |
-
for k, v in d.items():
|
304 |
-
setattr(self, k, v)
|
305 |
-
for k in excluded:
|
306 |
-
setattr(self, k, None)
|
307 |
-
|
308 |
-
|
309 |
-
## -- End Utilities -- @}
|
310 |
-
|
311 |
-
_USE_SHELL_DEFAULT_MESSAGE = (
|
312 |
-
"Git.USE_SHELL is deprecated, because only its default value of False is safe. "
|
313 |
-
"It will be removed in a future release."
|
314 |
-
)
|
315 |
-
|
316 |
-
_USE_SHELL_DANGER_MESSAGE = (
|
317 |
-
"Setting Git.USE_SHELL to True is unsafe and insecure, as the effect of special "
|
318 |
-
"shell syntax cannot usually be accounted for. This can result in a command "
|
319 |
-
"injection vulnerability and arbitrary code execution. Git.USE_SHELL is deprecated "
|
320 |
-
"and will be removed in a future release."
|
321 |
-
)
|
322 |
-
|
323 |
-
|
324 |
-
def _warn_use_shell(extra_danger: bool) -> None:
|
325 |
-
warnings.warn(
|
326 |
-
_USE_SHELL_DANGER_MESSAGE if extra_danger else _USE_SHELL_DEFAULT_MESSAGE,
|
327 |
-
DeprecationWarning,
|
328 |
-
stacklevel=3,
|
329 |
-
)
|
330 |
-
|
331 |
-
|
332 |
-
class _GitMeta(type):
|
333 |
-
"""Metaclass for :class:`Git`.
|
334 |
-
|
335 |
-
This helps issue :class:`DeprecationWarning` if :attr:`Git.USE_SHELL` is used.
|
336 |
-
"""
|
337 |
-
|
338 |
-
def __getattribute(cls, name: str) -> Any:
|
339 |
-
if name == "USE_SHELL":
|
340 |
-
_warn_use_shell(False)
|
341 |
-
return super().__getattribute__(name)
|
342 |
-
|
343 |
-
def __setattr(cls, name: str, value: Any) -> Any:
|
344 |
-
if name == "USE_SHELL":
|
345 |
-
_warn_use_shell(value)
|
346 |
-
super().__setattr__(name, value)
|
347 |
-
|
348 |
-
if not TYPE_CHECKING:
|
349 |
-
# To preserve static checking for undefined/misspelled attributes while letting
|
350 |
-
# the methods' bodies be type-checked, these are defined as non-special methods,
|
351 |
-
# then bound to special names out of view of static type checkers. (The original
|
352 |
-
# names invoke name mangling (leading "__") to avoid confusion in other scopes.)
|
353 |
-
__getattribute__ = __getattribute
|
354 |
-
__setattr__ = __setattr
|
355 |
-
|
356 |
-
|
357 |
-
GitMeta = _GitMeta
|
358 |
-
"""Alias of :class:`Git`'s metaclass, whether it is :class:`type` or a custom metaclass.
|
359 |
-
|
360 |
-
Whether the :class:`Git` class has the default :class:`type` as its metaclass or uses a
|
361 |
-
custom metaclass is not documented and may change at any time. This statically checkable
|
362 |
-
metaclass alias is equivalent at runtime to ``type(Git)``. This should almost never be
|
363 |
-
used. Code that benefits from it is likely to be remain brittle even if it is used.
|
364 |
-
|
365 |
-
In view of the :class:`Git` class's intended use and :class:`Git` objects' dynamic
|
366 |
-
callable attributes representing git subcommands, it rarely makes sense to inherit from
|
367 |
-
:class:`Git` at all. Using :class:`Git` in multiple inheritance can be especially tricky
|
368 |
-
to do correctly. Attempting uses of :class:`Git` where its metaclass is relevant, such
|
369 |
-
as when a sibling class has an unrelated metaclass and a shared lower bound metaclass
|
370 |
-
might have to be introduced to solve a metaclass conflict, is not recommended.
|
371 |
-
|
372 |
-
:note:
|
373 |
-
The correct static type of the :class:`Git` class itself, and any subclasses, is
|
374 |
-
``Type[Git]``. (This can be written as ``type[Git]`` in Python 3.9 later.)
|
375 |
-
|
376 |
-
:class:`GitMeta` should never be used in any annotation where ``Type[Git]`` is
|
377 |
-
intended or otherwise possible to use. This alias is truly only for very rare and
|
378 |
-
inherently precarious situations where it is necessary to deal with the metaclass
|
379 |
-
explicitly.
|
380 |
-
"""
|
381 |
-
|
382 |
-
|
383 |
-
class Git(metaclass=_GitMeta):
|
384 |
-
"""The Git class manages communication with the Git binary.
|
385 |
-
|
386 |
-
It provides a convenient interface to calling the Git binary, such as in::
|
387 |
-
|
388 |
-
g = Git( git_dir )
|
389 |
-
g.init() # calls 'git init' program
|
390 |
-
rval = g.ls_files() # calls 'git ls-files' program
|
391 |
-
|
392 |
-
Debugging:
|
393 |
-
|
394 |
-
* Set the :envvar:`GIT_PYTHON_TRACE` environment variable to print each invocation
|
395 |
-
of the command to stdout.
|
396 |
-
* Set its value to ``full`` to see details about the returned values.
|
397 |
-
"""
|
398 |
-
|
399 |
-
__slots__ = (
|
400 |
-
"_working_dir",
|
401 |
-
"cat_file_all",
|
402 |
-
"cat_file_header",
|
403 |
-
"_version_info",
|
404 |
-
"_version_info_token",
|
405 |
-
"_git_options",
|
406 |
-
"_persistent_git_options",
|
407 |
-
"_environment",
|
408 |
-
)
|
409 |
-
|
410 |
-
_excluded_ = (
|
411 |
-
"cat_file_all",
|
412 |
-
"cat_file_header",
|
413 |
-
"_version_info",
|
414 |
-
"_version_info_token",
|
415 |
-
)
|
416 |
-
|
417 |
-
re_unsafe_protocol = re.compile(r"(.+)::.+")
|
418 |
-
|
419 |
-
def __getstate__(self) -> Dict[str, Any]:
|
420 |
-
return slots_to_dict(self, exclude=self._excluded_)
|
421 |
-
|
422 |
-
def __setstate__(self, d: Dict[str, Any]) -> None:
|
423 |
-
dict_to_slots_and__excluded_are_none(self, d, excluded=self._excluded_)
|
424 |
-
|
425 |
-
# CONFIGURATION
|
426 |
-
|
427 |
-
git_exec_name = "git"
|
428 |
-
"""Default git command that should work on Linux, Windows, and other systems."""
|
429 |
-
|
430 |
-
GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)
|
431 |
-
"""Enables debugging of GitPython's git commands."""
|
432 |
-
|
433 |
-
USE_SHELL: bool = False
|
434 |
-
"""Deprecated. If set to ``True``, a shell will be used when executing git commands.
|
435 |
-
|
436 |
-
Code that uses ``USE_SHELL = True`` or that passes ``shell=True`` to any GitPython
|
437 |
-
functions should be updated to use the default value of ``False`` instead. ``True``
|
438 |
-
is unsafe unless the effect of syntax treated specially by the shell is fully
|
439 |
-
considered and accounted for, which is not possible under most circumstances. As
|
440 |
-
detailed below, it is also no longer needed, even where it had been in the past.
|
441 |
-
|
442 |
-
It is in many if not most cases a command injection vulnerability for an application
|
443 |
-
to set :attr:`USE_SHELL` to ``True``. Any attacker who can cause a specially crafted
|
444 |
-
fragment of text to make its way into any part of any argument to any git command
|
445 |
-
(including paths, branch names, etc.) can cause the shell to read and write
|
446 |
-
arbitrary files and execute arbitrary commands. Innocent input may also accidentally
|
447 |
-
contain special shell syntax, leading to inadvertent malfunctions.
|
448 |
-
|
449 |
-
In addition, how a value of ``True`` interacts with some aspects of GitPython's
|
450 |
-
operation is not precisely specified and may change without warning, even before
|
451 |
-
GitPython 4.0.0 when :attr:`USE_SHELL` may be removed. This includes:
|
452 |
-
|
453 |
-
* Whether or how GitPython automatically customizes the shell environment.
|
454 |
-
|
455 |
-
* Whether, outside of Windows (where :class:`subprocess.Popen` supports lists of
|
456 |
-
separate arguments even when ``shell=True``), this can be used with any GitPython
|
457 |
-
functionality other than direct calls to the :meth:`execute` method.
|
458 |
-
|
459 |
-
* Whether any GitPython feature that runs git commands ever attempts to partially
|
460 |
-
sanitize data a shell may treat specially. Currently this is not done.
|
461 |
-
|
462 |
-
Prior to GitPython 2.0.8, this had a narrow purpose in suppressing console windows
|
463 |
-
in graphical Windows applications. In 2.0.8 and higher, it provides no benefit, as
|
464 |
-
GitPython solves that problem more robustly and safely by using the
|
465 |
-
``CREATE_NO_WINDOW`` process creation flag on Windows.
|
466 |
-
|
467 |
-
Because Windows path search differs subtly based on whether a shell is used, in rare
|
468 |
-
cases changing this from ``True`` to ``False`` may keep an unusual git "executable",
|
469 |
-
such as a batch file, from being found. To fix this, set the command name or full
|
470 |
-
path in the :envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable or pass the
|
471 |
-
full path to :func:`git.refresh` (or invoke the script using a ``.exe`` shim).
|
472 |
-
|
473 |
-
Further reading:
|
474 |
-
|
475 |
-
* :meth:`Git.execute` (on the ``shell`` parameter).
|
476 |
-
* https://github.com/gitpython-developers/GitPython/commit/0d9390866f9ce42870d3116094cd49e0019a970a
|
477 |
-
* https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
|
478 |
-
* https://github.com/python/cpython/issues/91558#issuecomment-1100942950
|
479 |
-
* https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
|
480 |
-
"""
|
481 |
-
|
482 |
-
_git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE"
|
483 |
-
_refresh_env_var = "GIT_PYTHON_REFRESH"
|
484 |
-
|
485 |
-
GIT_PYTHON_GIT_EXECUTABLE = None
|
486 |
-
"""Provide the full path to the git executable. Otherwise it assumes git is in the
|
487 |
-
executable search path.
|
488 |
-
|
489 |
-
:note:
|
490 |
-
The git executable is actually found during the refresh step in the top level
|
491 |
-
``__init__``. It can also be changed by explicitly calling :func:`git.refresh`.
|
492 |
-
"""
|
493 |
-
|
494 |
-
_refresh_token = object() # Since None would match an initial _version_info_token.
|
495 |
-
|
496 |
-
@classmethod
|
497 |
-
def refresh(cls, path: Union[None, PathLike] = None) -> bool:
|
498 |
-
"""Update information about the git executable :class:`Git` objects will use.
|
499 |
-
|
500 |
-
Called by the :func:`git.refresh` function in the top level ``__init__``.
|
501 |
-
|
502 |
-
:param path:
|
503 |
-
Optional path to the git executable. If not absolute, it is resolved
|
504 |
-
immediately, relative to the current directory. (See note below.)
|
505 |
-
|
506 |
-
:note:
|
507 |
-
The top-level :func:`git.refresh` should be preferred because it calls this
|
508 |
-
method and may also update other state accordingly.
|
509 |
-
|
510 |
-
:note:
|
511 |
-
There are three different ways to specify the command that refreshing causes
|
512 |
-
to be used for git:
|
513 |
-
|
514 |
-
1. Pass no `path` argument and do not set the
|
515 |
-
:envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable. The command
|
516 |
-
name ``git`` is used. It is looked up in a path search by the system, in
|
517 |
-
each command run (roughly similar to how git is found when running
|
518 |
-
``git`` commands manually). This is usually the desired behavior.
|
519 |
-
|
520 |
-
2. Pass no `path` argument but set the :envvar:`GIT_PYTHON_GIT_EXECUTABLE`
|
521 |
-
environment variable. The command given as the value of that variable is
|
522 |
-
used. This may be a simple command or an arbitrary path. It is looked up
|
523 |
-
in each command run. Setting :envvar:`GIT_PYTHON_GIT_EXECUTABLE` to
|
524 |
-
``git`` has the same effect as not setting it.
|
525 |
-
|
526 |
-
3. Pass a `path` argument. This path, if not absolute, is immediately
|
527 |
-
resolved, relative to the current directory. This resolution occurs at
|
528 |
-
the time of the refresh. When git commands are run, they are run using
|
529 |
-
that previously resolved path. If a `path` argument is passed, the
|
530 |
-
:envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable is not
|
531 |
-
consulted.
|
532 |
-
|
533 |
-
:note:
|
534 |
-
Refreshing always sets the :attr:`Git.GIT_PYTHON_GIT_EXECUTABLE` class
|
535 |
-
attribute, which can be read on the :class:`Git` class or any of its
|
536 |
-
instances to check what command is used to run git. This attribute should
|
537 |
-
not be confused with the related :envvar:`GIT_PYTHON_GIT_EXECUTABLE`
|
538 |
-
environment variable. The class attribute is set no matter how refreshing is
|
539 |
-
performed.
|
540 |
-
"""
|
541 |
-
# Discern which path to refresh with.
|
542 |
-
if path is not None:
|
543 |
-
new_git = os.path.expanduser(path)
|
544 |
-
new_git = os.path.abspath(new_git)
|
545 |
-
else:
|
546 |
-
new_git = os.environ.get(cls._git_exec_env_var, cls.git_exec_name)
|
547 |
-
|
548 |
-
# Keep track of the old and new git executable path.
|
549 |
-
old_git = cls.GIT_PYTHON_GIT_EXECUTABLE
|
550 |
-
old_refresh_token = cls._refresh_token
|
551 |
-
cls.GIT_PYTHON_GIT_EXECUTABLE = new_git
|
552 |
-
cls._refresh_token = object()
|
553 |
-
|
554 |
-
# Test if the new git executable path is valid. A GitCommandNotFound error is
|
555 |
-
# raised by us. A PermissionError is raised if the git executable cannot be
|
556 |
-
# executed for whatever reason.
|
557 |
-
has_git = False
|
558 |
-
try:
|
559 |
-
cls().version()
|
560 |
-
has_git = True
|
561 |
-
except (GitCommandNotFound, PermissionError):
|
562 |
-
pass
|
563 |
-
|
564 |
-
# Warn or raise exception if test failed.
|
565 |
-
if not has_git:
|
566 |
-
err = (
|
567 |
-
dedent(
|
568 |
-
"""\
|
569 |
-
Bad git executable.
|
570 |
-
The git executable must be specified in one of the following ways:
|
571 |
-
- be included in your $PATH
|
572 |
-
- be set via $%s
|
573 |
-
- explicitly set via git.refresh(<full-path-to-git-executable>)
|
574 |
-
"""
|
575 |
-
)
|
576 |
-
% cls._git_exec_env_var
|
577 |
-
)
|
578 |
-
|
579 |
-
# Revert to whatever the old_git was.
|
580 |
-
cls.GIT_PYTHON_GIT_EXECUTABLE = old_git
|
581 |
-
cls._refresh_token = old_refresh_token
|
582 |
-
|
583 |
-
if old_git is None:
|
584 |
-
# On the first refresh (when GIT_PYTHON_GIT_EXECUTABLE is None) we only
|
585 |
-
# are quiet, warn, or error depending on the GIT_PYTHON_REFRESH value.
|
586 |
-
|
587 |
-
# Determine what the user wants to happen during the initial refresh. We
|
588 |
-
# expect GIT_PYTHON_REFRESH to either be unset or be one of the
|
589 |
-
# following values:
|
590 |
-
#
|
591 |
-
# 0|q|quiet|s|silence|silent|n|none
|
592 |
-
# 1|w|warn|warning|l|log
|
593 |
-
# 2|r|raise|e|error|exception
|
594 |
-
|
595 |
-
mode = os.environ.get(cls._refresh_env_var, "raise").lower()
|
596 |
-
|
597 |
-
quiet = ["quiet", "q", "silence", "s", "silent", "none", "n", "0"]
|
598 |
-
warn = ["warn", "w", "warning", "log", "l", "1"]
|
599 |
-
error = ["error", "e", "exception", "raise", "r", "2"]
|
600 |
-
|
601 |
-
if mode in quiet:
|
602 |
-
pass
|
603 |
-
elif mode in warn or mode in error:
|
604 |
-
err = dedent(
|
605 |
-
"""\
|
606 |
-
%s
|
607 |
-
All git commands will error until this is rectified.
|
608 |
-
|
609 |
-
This initial message can be silenced or aggravated in the future by setting the
|
610 |
-
$%s environment variable. Use one of the following values:
|
611 |
-
- %s: for no message or exception
|
612 |
-
- %s: for a warning message (logging level CRITICAL, displayed by default)
|
613 |
-
- %s: for a raised exception
|
614 |
-
|
615 |
-
Example:
|
616 |
-
export %s=%s
|
617 |
-
"""
|
618 |
-
) % (
|
619 |
-
err,
|
620 |
-
cls._refresh_env_var,
|
621 |
-
"|".join(quiet),
|
622 |
-
"|".join(warn),
|
623 |
-
"|".join(error),
|
624 |
-
cls._refresh_env_var,
|
625 |
-
quiet[0],
|
626 |
-
)
|
627 |
-
|
628 |
-
if mode in warn:
|
629 |
-
_logger.critical(err)
|
630 |
-
else:
|
631 |
-
raise ImportError(err)
|
632 |
-
else:
|
633 |
-
err = dedent(
|
634 |
-
"""\
|
635 |
-
%s environment variable has been set but it has been set with an invalid value.
|
636 |
-
|
637 |
-
Use only the following values:
|
638 |
-
- %s: for no message or exception
|
639 |
-
- %s: for a warning message (logging level CRITICAL, displayed by default)
|
640 |
-
- %s: for a raised exception
|
641 |
-
"""
|
642 |
-
) % (
|
643 |
-
cls._refresh_env_var,
|
644 |
-
"|".join(quiet),
|
645 |
-
"|".join(warn),
|
646 |
-
"|".join(error),
|
647 |
-
)
|
648 |
-
raise ImportError(err)
|
649 |
-
|
650 |
-
# We get here if this was the initial refresh and the refresh mode was
|
651 |
-
# not error. Go ahead and set the GIT_PYTHON_GIT_EXECUTABLE such that we
|
652 |
-
# discern the difference between the first refresh at import time
|
653 |
-
# and subsequent calls to git.refresh or this refresh method.
|
654 |
-
cls.GIT_PYTHON_GIT_EXECUTABLE = cls.git_exec_name
|
655 |
-
else:
|
656 |
-
# After the first refresh (when GIT_PYTHON_GIT_EXECUTABLE is no longer
|
657 |
-
# None) we raise an exception.
|
658 |
-
raise GitCommandNotFound(new_git, err)
|
659 |
-
|
660 |
-
return has_git
|
661 |
-
|
662 |
-
@classmethod
|
663 |
-
def is_cygwin(cls) -> bool:
|
664 |
-
return is_cygwin_git(cls.GIT_PYTHON_GIT_EXECUTABLE)
|
665 |
-
|
666 |
-
@overload
|
667 |
-
@classmethod
|
668 |
-
def polish_url(cls, url: str, is_cygwin: Literal[False] = ...) -> str: ...
|
669 |
-
|
670 |
-
@overload
|
671 |
-
@classmethod
|
672 |
-
def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> str: ...
|
673 |
-
|
674 |
-
@classmethod
|
675 |
-
def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> PathLike:
|
676 |
-
"""Remove any backslashes from URLs to be written in config files.
|
677 |
-
|
678 |
-
Windows might create config files containing paths with backslashes, but git
|
679 |
-
stops liking them as it will escape the backslashes. Hence we undo the escaping
|
680 |
-
just to be sure.
|
681 |
-
"""
|
682 |
-
if is_cygwin is None:
|
683 |
-
is_cygwin = cls.is_cygwin()
|
684 |
-
|
685 |
-
if is_cygwin:
|
686 |
-
url = cygpath(url)
|
687 |
-
else:
|
688 |
-
url = os.path.expandvars(url)
|
689 |
-
if url.startswith("~"):
|
690 |
-
url = os.path.expanduser(url)
|
691 |
-
url = url.replace("\\\\", "\\").replace("\\", "/")
|
692 |
-
return url
|
693 |
-
|
694 |
-
@classmethod
|
695 |
-
def check_unsafe_protocols(cls, url: str) -> None:
|
696 |
-
"""Check for unsafe protocols.
|
697 |
-
|
698 |
-
Apart from the usual protocols (http, git, ssh), Git allows "remote helpers"
|
699 |
-
that have the form ``<transport>::<address>``. One of these helpers (``ext::``)
|
700 |
-
can be used to invoke any arbitrary command.
|
701 |
-
|
702 |
-
See:
|
703 |
-
|
704 |
-
- https://git-scm.com/docs/gitremote-helpers
|
705 |
-
- https://git-scm.com/docs/git-remote-ext
|
706 |
-
"""
|
707 |
-
match = cls.re_unsafe_protocol.match(url)
|
708 |
-
if match:
|
709 |
-
protocol = match.group(1)
|
710 |
-
raise UnsafeProtocolError(
|
711 |
-
f"The `{protocol}::` protocol looks suspicious, use `allow_unsafe_protocols=True` to allow it."
|
712 |
-
)
|
713 |
-
|
714 |
-
@classmethod
|
715 |
-
def check_unsafe_options(cls, options: List[str], unsafe_options: List[str]) -> None:
|
716 |
-
"""Check for unsafe options.
|
717 |
-
|
718 |
-
Some options that are passed to ``git <command>`` can be used to execute
|
719 |
-
arbitrary commands. These are blocked by default.
|
720 |
-
"""
|
721 |
-
# Options can be of the form `foo`, `--foo bar`, or `--foo=bar`, so we need to
|
722 |
-
# check if they start with "--foo" or if they are equal to "foo".
|
723 |
-
bare_unsafe_options = [option.lstrip("-") for option in unsafe_options]
|
724 |
-
for option in options:
|
725 |
-
for unsafe_option, bare_option in zip(unsafe_options, bare_unsafe_options):
|
726 |
-
if option.startswith(unsafe_option) or option == bare_option:
|
727 |
-
raise UnsafeOptionError(
|
728 |
-
f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it."
|
729 |
-
)
|
730 |
-
|
731 |
-
class AutoInterrupt:
|
732 |
-
"""Process wrapper that terminates the wrapped process on finalization.
|
733 |
-
|
734 |
-
This kills/interrupts the stored process instance once this instance goes out of
|
735 |
-
scope. It is used to prevent processes piling up in case iterators stop reading.
|
736 |
-
|
737 |
-
All attributes are wired through to the contained process object.
|
738 |
-
|
739 |
-
The wait method is overridden to perform automatic status code checking and
|
740 |
-
possibly raise.
|
741 |
-
"""
|
742 |
-
|
743 |
-
__slots__ = ("proc", "args", "status")
|
744 |
-
|
745 |
-
# If this is non-zero it will override any status code during _terminate, used
|
746 |
-
# to prevent race conditions in testing.
|
747 |
-
_status_code_if_terminate: int = 0
|
748 |
-
|
749 |
-
def __init__(self, proc: Union[None, subprocess.Popen], args: Any) -> None:
|
750 |
-
self.proc = proc
|
751 |
-
self.args = args
|
752 |
-
self.status: Union[int, None] = None
|
753 |
-
|
754 |
-
def _terminate(self) -> None:
|
755 |
-
"""Terminate the underlying process."""
|
756 |
-
if self.proc is None:
|
757 |
-
return
|
758 |
-
|
759 |
-
proc = self.proc
|
760 |
-
self.proc = None
|
761 |
-
if proc.stdin:
|
762 |
-
proc.stdin.close()
|
763 |
-
if proc.stdout:
|
764 |
-
proc.stdout.close()
|
765 |
-
if proc.stderr:
|
766 |
-
proc.stderr.close()
|
767 |
-
# Did the process finish already so we have a return code?
|
768 |
-
try:
|
769 |
-
if proc.poll() is not None:
|
770 |
-
self.status = self._status_code_if_terminate or proc.poll()
|
771 |
-
return
|
772 |
-
except OSError as ex:
|
773 |
-
_logger.info("Ignored error after process had died: %r", ex)
|
774 |
-
|
775 |
-
# It can be that nothing really exists anymore...
|
776 |
-
if os is None or getattr(os, "kill", None) is None:
|
777 |
-
return
|
778 |
-
|
779 |
-
# Try to kill it.
|
780 |
-
try:
|
781 |
-
proc.terminate()
|
782 |
-
status = proc.wait() # Ensure the process goes away.
|
783 |
-
|
784 |
-
self.status = self._status_code_if_terminate or status
|
785 |
-
except OSError as ex:
|
786 |
-
_logger.info("Ignored error after process had died: %r", ex)
|
787 |
-
# END exception handling
|
788 |
-
|
789 |
-
def __del__(self) -> None:
|
790 |
-
self._terminate()
|
791 |
-
|
792 |
-
def __getattr__(self, attr: str) -> Any:
|
793 |
-
return getattr(self.proc, attr)
|
794 |
-
|
795 |
-
# TODO: Bad choice to mimic `proc.wait()` but with different args.
|
796 |
-
def wait(self, stderr: Union[None, str, bytes] = b"") -> int:
|
797 |
-
"""Wait for the process and return its status code.
|
798 |
-
|
799 |
-
:param stderr:
|
800 |
-
Previously read value of stderr, in case stderr is already closed.
|
801 |
-
|
802 |
-
:warn:
|
803 |
-
May deadlock if output or error pipes are used and not handled
|
804 |
-
separately.
|
805 |
-
|
806 |
-
:raise git.exc.GitCommandError:
|
807 |
-
If the return status is not 0.
|
808 |
-
"""
|
809 |
-
if stderr is None:
|
810 |
-
stderr_b = b""
|
811 |
-
stderr_b = force_bytes(data=stderr, encoding="utf-8")
|
812 |
-
status: Union[int, None]
|
813 |
-
if self.proc is not None:
|
814 |
-
status = self.proc.wait()
|
815 |
-
p_stderr = self.proc.stderr
|
816 |
-
else: # Assume the underlying proc was killed earlier or never existed.
|
817 |
-
status = self.status
|
818 |
-
p_stderr = None
|
819 |
-
|
820 |
-
def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes:
|
821 |
-
if stream:
|
822 |
-
try:
|
823 |
-
return stderr_b + force_bytes(stream.read())
|
824 |
-
except (OSError, ValueError):
|
825 |
-
return stderr_b or b""
|
826 |
-
else:
|
827 |
-
return stderr_b or b""
|
828 |
-
|
829 |
-
# END status handling
|
830 |
-
|
831 |
-
if status != 0:
|
832 |
-
errstr = read_all_from_possibly_closed_stream(p_stderr)
|
833 |
-
_logger.debug("AutoInterrupt wait stderr: %r" % (errstr,))
|
834 |
-
raise GitCommandError(remove_password_if_present(self.args), status, errstr)
|
835 |
-
return status
|
836 |
-
|
837 |
-
# END auto interrupt
|
838 |
-
|
839 |
-
class CatFileContentStream:
|
840 |
-
"""Object representing a sized read-only stream returning the contents of
|
841 |
-
an object.
|
842 |
-
|
843 |
-
This behaves like a stream, but counts the data read and simulates an empty
|
844 |
-
stream once our sized content region is empty.
|
845 |
-
|
846 |
-
If not all data are read to the end of the object's lifetime, we read the
|
847 |
-
rest to ensure the underlying stream continues to work.
|
848 |
-
"""
|
849 |
-
|
850 |
-
__slots__ = ("_stream", "_nbr", "_size")
|
851 |
-
|
852 |
-
def __init__(self, size: int, stream: IO[bytes]) -> None:
|
853 |
-
self._stream = stream
|
854 |
-
self._size = size
|
855 |
-
self._nbr = 0 # Number of bytes read.
|
856 |
-
|
857 |
-
# Special case: If the object is empty, has null bytes, get the final
|
858 |
-
# newline right away.
|
859 |
-
if size == 0:
|
860 |
-
stream.read(1)
|
861 |
-
# END handle empty streams
|
862 |
-
|
863 |
-
def read(self, size: int = -1) -> bytes:
|
864 |
-
bytes_left = self._size - self._nbr
|
865 |
-
if bytes_left == 0:
|
866 |
-
return b""
|
867 |
-
if size > -1:
|
868 |
-
# Ensure we don't try to read past our limit.
|
869 |
-
size = min(bytes_left, size)
|
870 |
-
else:
|
871 |
-
# They try to read all, make sure it's not more than what remains.
|
872 |
-
size = bytes_left
|
873 |
-
# END check early depletion
|
874 |
-
data = self._stream.read(size)
|
875 |
-
self._nbr += len(data)
|
876 |
-
|
877 |
-
# Check for depletion, read our final byte to make the stream usable by
|
878 |
-
# others.
|
879 |
-
if self._size - self._nbr == 0:
|
880 |
-
self._stream.read(1) # final newline
|
881 |
-
# END finish reading
|
882 |
-
return data
|
883 |
-
|
884 |
-
def readline(self, size: int = -1) -> bytes:
|
885 |
-
if self._nbr == self._size:
|
886 |
-
return b""
|
887 |
-
|
888 |
-
# Clamp size to lowest allowed value.
|
889 |
-
bytes_left = self._size - self._nbr
|
890 |
-
if size > -1:
|
891 |
-
size = min(bytes_left, size)
|
892 |
-
else:
|
893 |
-
size = bytes_left
|
894 |
-
# END handle size
|
895 |
-
|
896 |
-
data = self._stream.readline(size)
|
897 |
-
self._nbr += len(data)
|
898 |
-
|
899 |
-
# Handle final byte.
|
900 |
-
if self._size - self._nbr == 0:
|
901 |
-
self._stream.read(1)
|
902 |
-
# END finish reading
|
903 |
-
|
904 |
-
return data
|
905 |
-
|
906 |
-
def readlines(self, size: int = -1) -> List[bytes]:
|
907 |
-
if self._nbr == self._size:
|
908 |
-
return []
|
909 |
-
|
910 |
-
# Leave all additional logic to our readline method, we just check the size.
|
911 |
-
out = []
|
912 |
-
nbr = 0
|
913 |
-
while True:
|
914 |
-
line = self.readline()
|
915 |
-
if not line:
|
916 |
-
break
|
917 |
-
out.append(line)
|
918 |
-
if size > -1:
|
919 |
-
nbr += len(line)
|
920 |
-
if nbr > size:
|
921 |
-
break
|
922 |
-
# END handle size constraint
|
923 |
-
# END readline loop
|
924 |
-
return out
|
925 |
-
|
926 |
-
# skipcq: PYL-E0301
|
927 |
-
def __iter__(self) -> "Git.CatFileContentStream":
|
928 |
-
return self
|
929 |
-
|
930 |
-
def __next__(self) -> bytes:
|
931 |
-
line = self.readline()
|
932 |
-
if not line:
|
933 |
-
raise StopIteration
|
934 |
-
|
935 |
-
return line
|
936 |
-
|
937 |
-
next = __next__
|
938 |
-
|
939 |
-
def __del__(self) -> None:
|
940 |
-
bytes_left = self._size - self._nbr
|
941 |
-
if bytes_left:
|
942 |
-
# Read and discard - seeking is impossible within a stream.
|
943 |
-
# This includes any terminating newline.
|
944 |
-
self._stream.read(bytes_left + 1)
|
945 |
-
# END handle incomplete read
|
946 |
-
|
947 |
-
def __init__(self, working_dir: Union[None, PathLike] = None) -> None:
|
948 |
-
"""Initialize this instance with:
|
949 |
-
|
950 |
-
:param working_dir:
|
951 |
-
Git directory we should work in. If ``None``, we always work in the current
|
952 |
-
directory as returned by :func:`os.getcwd`.
|
953 |
-
This is meant to be the working tree directory if available, or the
|
954 |
-
``.git`` directory in case of bare repositories.
|
955 |
-
"""
|
956 |
-
super().__init__()
|
957 |
-
self._working_dir = expand_path(working_dir)
|
958 |
-
self._git_options: Union[List[str], Tuple[str, ...]] = ()
|
959 |
-
self._persistent_git_options: List[str] = []
|
960 |
-
|
961 |
-
# Extra environment variables to pass to git commands
|
962 |
-
self._environment: Dict[str, str] = {}
|
963 |
-
|
964 |
-
# Cached version slots
|
965 |
-
self._version_info: Union[Tuple[int, ...], None] = None
|
966 |
-
self._version_info_token: object = None
|
967 |
-
|
968 |
-
# Cached command slots
|
969 |
-
self.cat_file_header: Union[None, TBD] = None
|
970 |
-
self.cat_file_all: Union[None, TBD] = None
|
971 |
-
|
972 |
-
def __getattribute__(self, name: str) -> Any:
|
973 |
-
if name == "USE_SHELL":
|
974 |
-
_warn_use_shell(False)
|
975 |
-
return super().__getattribute__(name)
|
976 |
-
|
977 |
-
def __getattr__(self, name: str) -> Any:
|
978 |
-
"""A convenience method as it allows to call the command as if it was an object.
|
979 |
-
|
980 |
-
:return:
|
981 |
-
Callable object that will execute call :meth:`_call_process` with your
|
982 |
-
arguments.
|
983 |
-
"""
|
984 |
-
if name.startswith("_"):
|
985 |
-
return super().__getattribute__(name)
|
986 |
-
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
|
987 |
-
|
988 |
-
def set_persistent_git_options(self, **kwargs: Any) -> None:
|
989 |
-
"""Specify command line options to the git executable for subsequent
|
990 |
-
subcommand calls.
|
991 |
-
|
992 |
-
:param kwargs:
|
993 |
-
A dict of keyword arguments.
|
994 |
-
These arguments are passed as in :meth:`_call_process`, but will be passed
|
995 |
-
to the git command rather than the subcommand.
|
996 |
-
"""
|
997 |
-
|
998 |
-
self._persistent_git_options = self.transform_kwargs(split_single_char_options=True, **kwargs)
|
999 |
-
|
1000 |
-
@property
|
1001 |
-
def working_dir(self) -> Union[None, PathLike]:
|
1002 |
-
""":return: Git directory we are working on"""
|
1003 |
-
return self._working_dir
|
1004 |
-
|
1005 |
-
@property
|
1006 |
-
def version_info(self) -> Tuple[int, ...]:
|
1007 |
-
"""
|
1008 |
-
:return: Tuple with integers representing the major, minor and additional
|
1009 |
-
version numbers as parsed from :manpage:`git-version(1)`. Up to four fields
|
1010 |
-
are used.
|
1011 |
-
|
1012 |
-
This value is generated on demand and is cached.
|
1013 |
-
"""
|
1014 |
-
# Refreshing is global, but version_info caching is per-instance.
|
1015 |
-
refresh_token = self._refresh_token # Copy token in case of concurrent refresh.
|
1016 |
-
|
1017 |
-
# Use the cached version if obtained after the most recent refresh.
|
1018 |
-
if self._version_info_token is refresh_token:
|
1019 |
-
assert self._version_info is not None, "Bug: corrupted token-check state"
|
1020 |
-
return self._version_info
|
1021 |
-
|
1022 |
-
# Run "git version" and parse it.
|
1023 |
-
process_version = self._call_process("version")
|
1024 |
-
version_string = process_version.split(" ")[2]
|
1025 |
-
version_fields = version_string.split(".")[:4]
|
1026 |
-
leading_numeric_fields = itertools.takewhile(str.isdigit, version_fields)
|
1027 |
-
self._version_info = tuple(map(int, leading_numeric_fields))
|
1028 |
-
|
1029 |
-
# This value will be considered valid until the next refresh.
|
1030 |
-
self._version_info_token = refresh_token
|
1031 |
-
return self._version_info
|
1032 |
-
|
1033 |
-
@overload
|
1034 |
-
def execute(
|
1035 |
-
self,
|
1036 |
-
command: Union[str, Sequence[Any]],
|
1037 |
-
*,
|
1038 |
-
as_process: Literal[True],
|
1039 |
-
) -> "AutoInterrupt": ...
|
1040 |
-
|
1041 |
-
@overload
|
1042 |
-
def execute(
|
1043 |
-
self,
|
1044 |
-
command: Union[str, Sequence[Any]],
|
1045 |
-
*,
|
1046 |
-
as_process: Literal[False] = False,
|
1047 |
-
stdout_as_string: Literal[True],
|
1048 |
-
) -> Union[str, Tuple[int, str, str]]: ...
|
1049 |
-
|
1050 |
-
@overload
|
1051 |
-
def execute(
|
1052 |
-
self,
|
1053 |
-
command: Union[str, Sequence[Any]],
|
1054 |
-
*,
|
1055 |
-
as_process: Literal[False] = False,
|
1056 |
-
stdout_as_string: Literal[False] = False,
|
1057 |
-
) -> Union[bytes, Tuple[int, bytes, str]]: ...
|
1058 |
-
|
1059 |
-
@overload
|
1060 |
-
def execute(
|
1061 |
-
self,
|
1062 |
-
command: Union[str, Sequence[Any]],
|
1063 |
-
*,
|
1064 |
-
with_extended_output: Literal[False],
|
1065 |
-
as_process: Literal[False],
|
1066 |
-
stdout_as_string: Literal[True],
|
1067 |
-
) -> str: ...
|
1068 |
-
|
1069 |
-
@overload
|
1070 |
-
def execute(
|
1071 |
-
self,
|
1072 |
-
command: Union[str, Sequence[Any]],
|
1073 |
-
*,
|
1074 |
-
with_extended_output: Literal[False],
|
1075 |
-
as_process: Literal[False],
|
1076 |
-
stdout_as_string: Literal[False],
|
1077 |
-
) -> bytes: ...
|
1078 |
-
|
1079 |
-
def execute(
|
1080 |
-
self,
|
1081 |
-
command: Union[str, Sequence[Any]],
|
1082 |
-
istream: Union[None, BinaryIO] = None,
|
1083 |
-
with_extended_output: bool = False,
|
1084 |
-
with_exceptions: bool = True,
|
1085 |
-
as_process: bool = False,
|
1086 |
-
output_stream: Union[None, BinaryIO] = None,
|
1087 |
-
stdout_as_string: bool = True,
|
1088 |
-
kill_after_timeout: Union[None, float] = None,
|
1089 |
-
with_stdout: bool = True,
|
1090 |
-
universal_newlines: bool = False,
|
1091 |
-
shell: Union[None, bool] = None,
|
1092 |
-
env: Union[None, Mapping[str, str]] = None,
|
1093 |
-
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
|
1094 |
-
strip_newline_in_stdout: bool = True,
|
1095 |
-
**subprocess_kwargs: Any,
|
1096 |
-
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
|
1097 |
-
R"""Handle executing the command, and consume and return the returned
|
1098 |
-
information (stdout).
|
1099 |
-
|
1100 |
-
:param command:
|
1101 |
-
The command argument list to execute.
|
1102 |
-
It should be a sequence of program arguments, or a string. The
|
1103 |
-
program to execute is the first item in the args sequence or string.
|
1104 |
-
|
1105 |
-
:param istream:
|
1106 |
-
Standard input filehandle passed to :class:`subprocess.Popen`.
|
1107 |
-
|
1108 |
-
:param with_extended_output:
|
1109 |
-
Whether to return a (status, stdout, stderr) tuple.
|
1110 |
-
|
1111 |
-
:param with_exceptions:
|
1112 |
-
Whether to raise an exception when git returns a non-zero status.
|
1113 |
-
|
1114 |
-
:param as_process:
|
1115 |
-
Whether to return the created process instance directly from which
|
1116 |
-
streams can be read on demand. This will render `with_extended_output`
|
1117 |
-
and `with_exceptions` ineffective - the caller will have to deal with
|
1118 |
-
the details. It is important to note that the process will be placed
|
1119 |
-
into an :class:`AutoInterrupt` wrapper that will interrupt the process
|
1120 |
-
once it goes out of scope. If you use the command in iterators, you
|
1121 |
-
should pass the whole process instance instead of a single stream.
|
1122 |
-
|
1123 |
-
:param output_stream:
|
1124 |
-
If set to a file-like object, data produced by the git command will be
|
1125 |
-
copied to the given stream instead of being returned as a string.
|
1126 |
-
This feature only has any effect if `as_process` is ``False``.
|
1127 |
-
|
1128 |
-
:param stdout_as_string:
|
1129 |
-
If ``False``, the command's standard output will be bytes. Otherwise, it
|
1130 |
-
will be decoded into a string using the default encoding (usually UTF-8).
|
1131 |
-
The latter can fail, if the output contains binary data.
|
1132 |
-
|
1133 |
-
:param kill_after_timeout:
|
1134 |
-
Specifies a timeout in seconds for the git command, after which the process
|
1135 |
-
should be killed. This will have no effect if `as_process` is set to
|
1136 |
-
``True``. It is set to ``None`` by default and will let the process run
|
1137 |
-
until the timeout is explicitly specified. Uses of this feature should be
|
1138 |
-
carefully considered, due to the following limitations:
|
1139 |
-
|
1140 |
-
1. This feature is not supported at all on Windows.
|
1141 |
-
2. Effectiveness may vary by operating system. ``ps --ppid`` is used to
|
1142 |
-
enumerate child processes, which is available on most GNU/Linux systems
|
1143 |
-
but not most others.
|
1144 |
-
3. Deeper descendants do not receive signals, though they may sometimes
|
1145 |
-
terminate as a consequence of their parent processes being killed.
|
1146 |
-
4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side
|
1147 |
-
effects on a repository. For example, stale locks in case of
|
1148 |
-
:manpage:`git-gc(1)` could render the repository incapable of accepting
|
1149 |
-
changes until the lock is manually removed.
|
1150 |
-
|
1151 |
-
:param with_stdout:
|
1152 |
-
If ``True``, default ``True``, we open stdout on the created process.
|
1153 |
-
|
1154 |
-
:param universal_newlines:
|
1155 |
-
If ``True``, pipes will be opened as text, and lines are split at all known
|
1156 |
-
line endings.
|
1157 |
-
|
1158 |
-
:param shell:
|
1159 |
-
Whether to invoke commands through a shell
|
1160 |
-
(see :class:`Popen(..., shell=True) <subprocess.Popen>`).
|
1161 |
-
If this is not ``None``, it overrides :attr:`USE_SHELL`.
|
1162 |
-
|
1163 |
-
Passing ``shell=True`` to this or any other GitPython function should be
|
1164 |
-
avoided, as it is unsafe under most circumstances. This is because it is
|
1165 |
-
typically not feasible to fully consider and account for the effect of shell
|
1166 |
-
expansions, especially when passing ``shell=True`` to other methods that
|
1167 |
-
forward it to :meth:`Git.execute`. Passing ``shell=True`` is also no longer
|
1168 |
-
needed (nor useful) to work around any known operating system specific
|
1169 |
-
issues.
|
1170 |
-
|
1171 |
-
:param env:
|
1172 |
-
A dictionary of environment variables to be passed to
|
1173 |
-
:class:`subprocess.Popen`.
|
1174 |
-
|
1175 |
-
:param max_chunk_size:
|
1176 |
-
Maximum number of bytes in one chunk of data passed to the `output_stream`
|
1177 |
-
in one invocation of its ``write()`` method. If the given number is not
|
1178 |
-
positive then the default value is used.
|
1179 |
-
|
1180 |
-
:param strip_newline_in_stdout:
|
1181 |
-
Whether to strip the trailing ``\n`` of the command stdout.
|
1182 |
-
|
1183 |
-
:param subprocess_kwargs:
|
1184 |
-
Keyword arguments to be passed to :class:`subprocess.Popen`. Please note
|
1185 |
-
that some of the valid kwargs are already set by this method; the ones you
|
1186 |
-
specify may not be the same ones.
|
1187 |
-
|
1188 |
-
:return:
|
1189 |
-
* str(output), if `extended_output` is ``False`` (Default)
|
1190 |
-
* tuple(int(status), str(stdout), str(stderr)),
|
1191 |
-
if `extended_output` is ``True``
|
1192 |
-
|
1193 |
-
If `output_stream` is ``True``, the stdout value will be your output stream:
|
1194 |
-
|
1195 |
-
* output_stream, if `extended_output` is ``False``
|
1196 |
-
* tuple(int(status), output_stream, str(stderr)),
|
1197 |
-
if `extended_output` is ``True``
|
1198 |
-
|
1199 |
-
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
|
1200 |
-
output regardless of system language.
|
1201 |
-
|
1202 |
-
:raise git.exc.GitCommandError:
|
1203 |
-
|
1204 |
-
:note:
|
1205 |
-
If you add additional keyword arguments to the signature of this method, you
|
1206 |
-
must update the ``execute_kwargs`` variable housed in this module.
|
1207 |
-
"""
|
1208 |
-
# Remove password for the command if present.
|
1209 |
-
redacted_command = remove_password_if_present(command)
|
1210 |
-
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
|
1211 |
-
_logger.info(" ".join(redacted_command))
|
1212 |
-
|
1213 |
-
# Allow the user to have the command executed in their working dir.
|
1214 |
-
try:
|
1215 |
-
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
|
1216 |
-
if not os.access(str(cwd), os.X_OK):
|
1217 |
-
cwd = None
|
1218 |
-
except FileNotFoundError:
|
1219 |
-
cwd = None
|
1220 |
-
|
1221 |
-
# Start the process.
|
1222 |
-
inline_env = env
|
1223 |
-
env = os.environ.copy()
|
1224 |
-
# Attempt to force all output to plain ASCII English, which is what some parsing
|
1225 |
-
# code may expect.
|
1226 |
-
# According to https://askubuntu.com/a/311796, we are setting LANGUAGE as well
|
1227 |
-
# just to be sure.
|
1228 |
-
env["LANGUAGE"] = "C"
|
1229 |
-
env["LC_ALL"] = "C"
|
1230 |
-
env.update(self._environment)
|
1231 |
-
if inline_env is not None:
|
1232 |
-
env.update(inline_env)
|
1233 |
-
|
1234 |
-
if sys.platform == "win32":
|
1235 |
-
if kill_after_timeout is not None:
|
1236 |
-
raise GitCommandError(
|
1237 |
-
redacted_command,
|
1238 |
-
'"kill_after_timeout" feature is not supported on Windows.',
|
1239 |
-
)
|
1240 |
-
cmd_not_found_exception = OSError
|
1241 |
-
else:
|
1242 |
-
cmd_not_found_exception = FileNotFoundError
|
1243 |
-
# END handle
|
1244 |
-
|
1245 |
-
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
|
1246 |
-
if shell is None:
|
1247 |
-
# Get the value of USE_SHELL with no deprecation warning. Do this without
|
1248 |
-
# warnings.catch_warnings, to avoid a race condition with application code
|
1249 |
-
# configuring warnings. The value could be looked up in type(self).__dict__
|
1250 |
-
# or Git.__dict__, but those can break under some circumstances. This works
|
1251 |
-
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
|
1252 |
-
shell = super().__getattribute__("USE_SHELL")
|
1253 |
-
_logger.debug(
|
1254 |
-
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
|
1255 |
-
redacted_command,
|
1256 |
-
cwd,
|
1257 |
-
"<valid stream>" if istream else "None",
|
1258 |
-
shell,
|
1259 |
-
universal_newlines,
|
1260 |
-
)
|
1261 |
-
try:
|
1262 |
-
proc = safer_popen(
|
1263 |
-
command,
|
1264 |
-
env=env,
|
1265 |
-
cwd=cwd,
|
1266 |
-
bufsize=-1,
|
1267 |
-
stdin=(istream or DEVNULL),
|
1268 |
-
stderr=PIPE,
|
1269 |
-
stdout=stdout_sink,
|
1270 |
-
shell=shell,
|
1271 |
-
universal_newlines=universal_newlines,
|
1272 |
-
**subprocess_kwargs,
|
1273 |
-
)
|
1274 |
-
except cmd_not_found_exception as err:
|
1275 |
-
raise GitCommandNotFound(redacted_command, err) from err
|
1276 |
-
else:
|
1277 |
-
# Replace with a typeguard for Popen[bytes]?
|
1278 |
-
proc.stdout = cast(BinaryIO, proc.stdout)
|
1279 |
-
proc.stderr = cast(BinaryIO, proc.stderr)
|
1280 |
-
|
1281 |
-
if as_process:
|
1282 |
-
return self.AutoInterrupt(proc, command)
|
1283 |
-
|
1284 |
-
if sys.platform != "win32" and kill_after_timeout is not None:
|
1285 |
-
# Help mypy figure out this is not None even when used inside communicate().
|
1286 |
-
timeout = kill_after_timeout
|
1287 |
-
|
1288 |
-
def kill_process(pid: int) -> None:
|
1289 |
-
"""Callback to kill a process.
|
1290 |
-
|
1291 |
-
This callback implementation would be ineffective and unsafe on Windows.
|
1292 |
-
"""
|
1293 |
-
p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
|
1294 |
-
child_pids = []
|
1295 |
-
if p.stdout is not None:
|
1296 |
-
for line in p.stdout:
|
1297 |
-
if len(line.split()) > 0:
|
1298 |
-
local_pid = (line.split())[0]
|
1299 |
-
if local_pid.isdigit():
|
1300 |
-
child_pids.append(int(local_pid))
|
1301 |
-
try:
|
1302 |
-
os.kill(pid, signal.SIGKILL)
|
1303 |
-
for child_pid in child_pids:
|
1304 |
-
try:
|
1305 |
-
os.kill(child_pid, signal.SIGKILL)
|
1306 |
-
except OSError:
|
1307 |
-
pass
|
1308 |
-
# Tell the main routine that the process was killed.
|
1309 |
-
kill_check.set()
|
1310 |
-
except OSError:
|
1311 |
-
# It is possible that the process gets completed in the duration
|
1312 |
-
# after timeout happens and before we try to kill the process.
|
1313 |
-
pass
|
1314 |
-
return
|
1315 |
-
|
1316 |
-
def communicate() -> Tuple[AnyStr, AnyStr]:
|
1317 |
-
watchdog.start()
|
1318 |
-
out, err = proc.communicate()
|
1319 |
-
watchdog.cancel()
|
1320 |
-
if kill_check.is_set():
|
1321 |
-
err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
|
1322 |
-
" ".join(redacted_command),
|
1323 |
-
timeout,
|
1324 |
-
)
|
1325 |
-
if not universal_newlines:
|
1326 |
-
err = err.encode(defenc)
|
1327 |
-
return out, err
|
1328 |
-
|
1329 |
-
# END helpers
|
1330 |
-
|
1331 |
-
kill_check = threading.Event()
|
1332 |
-
watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
|
1333 |
-
else:
|
1334 |
-
communicate = proc.communicate
|
1335 |
-
|
1336 |
-
# Wait for the process to return.
|
1337 |
-
status = 0
|
1338 |
-
stdout_value: Union[str, bytes] = b""
|
1339 |
-
stderr_value: Union[str, bytes] = b""
|
1340 |
-
newline = "\n" if universal_newlines else b"\n"
|
1341 |
-
try:
|
1342 |
-
if output_stream is None:
|
1343 |
-
stdout_value, stderr_value = communicate()
|
1344 |
-
# Strip trailing "\n".
|
1345 |
-
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
|
1346 |
-
stdout_value = stdout_value[:-1]
|
1347 |
-
if stderr_value.endswith(newline): # type: ignore[arg-type]
|
1348 |
-
stderr_value = stderr_value[:-1]
|
1349 |
-
|
1350 |
-
status = proc.returncode
|
1351 |
-
else:
|
1352 |
-
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
|
1353 |
-
stream_copy(proc.stdout, output_stream, max_chunk_size)
|
1354 |
-
stdout_value = proc.stdout.read()
|
1355 |
-
stderr_value = proc.stderr.read()
|
1356 |
-
# Strip trailing "\n".
|
1357 |
-
if stderr_value.endswith(newline): # type: ignore[arg-type]
|
1358 |
-
stderr_value = stderr_value[:-1]
|
1359 |
-
status = proc.wait()
|
1360 |
-
# END stdout handling
|
1361 |
-
finally:
|
1362 |
-
proc.stdout.close()
|
1363 |
-
proc.stderr.close()
|
1364 |
-
|
1365 |
-
if self.GIT_PYTHON_TRACE == "full":
|
1366 |
-
cmdstr = " ".join(redacted_command)
|
1367 |
-
|
1368 |
-
def as_text(stdout_value: Union[bytes, str]) -> str:
|
1369 |
-
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
|
1370 |
-
|
1371 |
-
# END as_text
|
1372 |
-
|
1373 |
-
if stderr_value:
|
1374 |
-
_logger.info(
|
1375 |
-
"%s -> %d; stdout: '%s'; stderr: '%s'",
|
1376 |
-
cmdstr,
|
1377 |
-
status,
|
1378 |
-
as_text(stdout_value),
|
1379 |
-
safe_decode(stderr_value),
|
1380 |
-
)
|
1381 |
-
elif stdout_value:
|
1382 |
-
_logger.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
|
1383 |
-
else:
|
1384 |
-
_logger.info("%s -> %d", cmdstr, status)
|
1385 |
-
# END handle debug printing
|
1386 |
-
|
1387 |
-
if with_exceptions and status != 0:
|
1388 |
-
raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
|
1389 |
-
|
1390 |
-
if isinstance(stdout_value, bytes) and stdout_as_string: # Could also be output_stream.
|
1391 |
-
stdout_value = safe_decode(stdout_value)
|
1392 |
-
|
1393 |
-
# Allow access to the command's status code.
|
1394 |
-
if with_extended_output:
|
1395 |
-
return (status, stdout_value, safe_decode(stderr_value))
|
1396 |
-
else:
|
1397 |
-
return stdout_value
|
1398 |
-
|
1399 |
-
def environment(self) -> Dict[str, str]:
|
1400 |
-
return self._environment
|
1401 |
-
|
1402 |
-
def update_environment(self, **kwargs: Any) -> Dict[str, Union[str, None]]:
|
1403 |
-
"""Set environment variables for future git invocations. Return all changed
|
1404 |
-
values in a format that can be passed back into this function to revert the
|
1405 |
-
changes.
|
1406 |
-
|
1407 |
-
Examples::
|
1408 |
-
|
1409 |
-
old_env = self.update_environment(PWD='/tmp')
|
1410 |
-
self.update_environment(**old_env)
|
1411 |
-
|
1412 |
-
:param kwargs:
|
1413 |
-
Environment variables to use for git processes.
|
1414 |
-
|
1415 |
-
:return:
|
1416 |
-
Dict that maps environment variables to their old values
|
1417 |
-
"""
|
1418 |
-
old_env = {}
|
1419 |
-
for key, value in kwargs.items():
|
1420 |
-
# Set value if it is None.
|
1421 |
-
if value is not None:
|
1422 |
-
old_env[key] = self._environment.get(key)
|
1423 |
-
self._environment[key] = value
|
1424 |
-
# Remove key from environment if its value is None.
|
1425 |
-
elif key in self._environment:
|
1426 |
-
old_env[key] = self._environment[key]
|
1427 |
-
del self._environment[key]
|
1428 |
-
return old_env
|
1429 |
-
|
1430 |
-
@contextlib.contextmanager
|
1431 |
-
def custom_environment(self, **kwargs: Any) -> Iterator[None]:
|
1432 |
-
"""A context manager around the above :meth:`update_environment` method to
|
1433 |
-
restore the environment back to its previous state after operation.
|
1434 |
-
|
1435 |
-
Examples::
|
1436 |
-
|
1437 |
-
with self.custom_environment(GIT_SSH='/bin/ssh_wrapper'):
|
1438 |
-
repo.remotes.origin.fetch()
|
1439 |
-
|
1440 |
-
:param kwargs:
|
1441 |
-
See :meth:`update_environment`.
|
1442 |
-
"""
|
1443 |
-
old_env = self.update_environment(**kwargs)
|
1444 |
-
try:
|
1445 |
-
yield
|
1446 |
-
finally:
|
1447 |
-
self.update_environment(**old_env)
|
1448 |
-
|
1449 |
-
def transform_kwarg(self, name: str, value: Any, split_single_char_options: bool) -> List[str]:
|
1450 |
-
if len(name) == 1:
|
1451 |
-
if value is True:
|
1452 |
-
return ["-%s" % name]
|
1453 |
-
elif value not in (False, None):
|
1454 |
-
if split_single_char_options:
|
1455 |
-
return ["-%s" % name, "%s" % value]
|
1456 |
-
else:
|
1457 |
-
return ["-%s%s" % (name, value)]
|
1458 |
-
else:
|
1459 |
-
if value is True:
|
1460 |
-
return ["--%s" % dashify(name)]
|
1461 |
-
elif value is not False and value is not None:
|
1462 |
-
return ["--%s=%s" % (dashify(name), value)]
|
1463 |
-
return []
|
1464 |
-
|
1465 |
-
def transform_kwargs(self, split_single_char_options: bool = True, **kwargs: Any) -> List[str]:
|
1466 |
-
"""Transform Python-style kwargs into git command line options."""
|
1467 |
-
args = []
|
1468 |
-
for k, v in kwargs.items():
|
1469 |
-
if isinstance(v, (list, tuple)):
|
1470 |
-
for value in v:
|
1471 |
-
args += self.transform_kwarg(k, value, split_single_char_options)
|
1472 |
-
else:
|
1473 |
-
args += self.transform_kwarg(k, v, split_single_char_options)
|
1474 |
-
return args
|
1475 |
-
|
1476 |
-
@classmethod
|
1477 |
-
def _unpack_args(cls, arg_list: Sequence[str]) -> List[str]:
|
1478 |
-
outlist = []
|
1479 |
-
if isinstance(arg_list, (list, tuple)):
|
1480 |
-
for arg in arg_list:
|
1481 |
-
outlist.extend(cls._unpack_args(arg))
|
1482 |
-
else:
|
1483 |
-
outlist.append(str(arg_list))
|
1484 |
-
|
1485 |
-
return outlist
|
1486 |
-
|
1487 |
-
def __call__(self, **kwargs: Any) -> "Git":
|
1488 |
-
"""Specify command line options to the git executable for a subcommand call.
|
1489 |
-
|
1490 |
-
:param kwargs:
|
1491 |
-
A dict of keyword arguments.
|
1492 |
-
These arguments are passed as in :meth:`_call_process`, but will be passed
|
1493 |
-
to the git command rather than the subcommand.
|
1494 |
-
|
1495 |
-
Examples::
|
1496 |
-
|
1497 |
-
git(work_tree='/tmp').difftool()
|
1498 |
-
"""
|
1499 |
-
self._git_options = self.transform_kwargs(split_single_char_options=True, **kwargs)
|
1500 |
-
return self
|
1501 |
-
|
1502 |
-
@overload
|
1503 |
-
def _call_process(
|
1504 |
-
self, method: str, *args: None, **kwargs: None
|
1505 |
-
) -> str: ... # If no args were given, execute the call with all defaults.
|
1506 |
-
|
1507 |
-
@overload
|
1508 |
-
def _call_process(
|
1509 |
-
self,
|
1510 |
-
method: str,
|
1511 |
-
istream: int,
|
1512 |
-
as_process: Literal[True],
|
1513 |
-
*args: Any,
|
1514 |
-
**kwargs: Any,
|
1515 |
-
) -> "Git.AutoInterrupt": ...
|
1516 |
-
|
1517 |
-
@overload
|
1518 |
-
def _call_process(
|
1519 |
-
self, method: str, *args: Any, **kwargs: Any
|
1520 |
-
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]: ...
|
1521 |
-
|
1522 |
-
def _call_process(
|
1523 |
-
self, method: str, *args: Any, **kwargs: Any
|
1524 |
-
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]:
|
1525 |
-
"""Run the given git command with the specified arguments and return the result
|
1526 |
-
as a string.
|
1527 |
-
|
1528 |
-
:param method:
|
1529 |
-
The command. Contained ``_`` characters will be converted to hyphens, such
|
1530 |
-
as in ``ls_files`` to call ``ls-files``.
|
1531 |
-
|
1532 |
-
:param args:
|
1533 |
-
The list of arguments. If ``None`` is included, it will be pruned.
|
1534 |
-
This allows your commands to call git more conveniently, as ``None`` is
|
1535 |
-
realized as non-existent.
|
1536 |
-
|
1537 |
-
:param kwargs:
|
1538 |
-
Contains key-values for the following:
|
1539 |
-
|
1540 |
-
- The :meth:`execute()` kwds, as listed in ``execute_kwargs``.
|
1541 |
-
- "Command options" to be converted by :meth:`transform_kwargs`.
|
1542 |
-
- The ``insert_kwargs_after`` key which its value must match one of
|
1543 |
-
``*args``.
|
1544 |
-
|
1545 |
-
It also contains any command options, to be appended after the matched arg.
|
1546 |
-
|
1547 |
-
Examples::
|
1548 |
-
|
1549 |
-
git.rev_list('master', max_count=10, header=True)
|
1550 |
-
|
1551 |
-
turns into::
|
1552 |
-
|
1553 |
-
git rev-list max-count 10 --header master
|
1554 |
-
|
1555 |
-
:return:
|
1556 |
-
Same as :meth:`execute`. If no args are given, used :meth:`execute`'s
|
1557 |
-
default (especially ``as_process = False``, ``stdout_as_string = True``) and
|
1558 |
-
return :class:`str`.
|
1559 |
-
"""
|
1560 |
-
# Handle optional arguments prior to calling transform_kwargs.
|
1561 |
-
# Otherwise these'll end up in args, which is bad.
|
1562 |
-
exec_kwargs = {k: v for k, v in kwargs.items() if k in execute_kwargs}
|
1563 |
-
opts_kwargs = {k: v for k, v in kwargs.items() if k not in execute_kwargs}
|
1564 |
-
|
1565 |
-
insert_after_this_arg = opts_kwargs.pop("insert_kwargs_after", None)
|
1566 |
-
|
1567 |
-
# Prepare the argument list.
|
1568 |
-
|
1569 |
-
opt_args = self.transform_kwargs(**opts_kwargs)
|
1570 |
-
ext_args = self._unpack_args([a for a in args if a is not None])
|
1571 |
-
|
1572 |
-
if insert_after_this_arg is None:
|
1573 |
-
args_list = opt_args + ext_args
|
1574 |
-
else:
|
1575 |
-
try:
|
1576 |
-
index = ext_args.index(insert_after_this_arg)
|
1577 |
-
except ValueError as err:
|
1578 |
-
raise ValueError(
|
1579 |
-
"Couldn't find argument '%s' in args %s to insert cmd options after"
|
1580 |
-
% (insert_after_this_arg, str(ext_args))
|
1581 |
-
) from err
|
1582 |
-
# END handle error
|
1583 |
-
args_list = ext_args[: index + 1] + opt_args + ext_args[index + 1 :]
|
1584 |
-
# END handle opts_kwargs
|
1585 |
-
|
1586 |
-
call = [self.GIT_PYTHON_GIT_EXECUTABLE]
|
1587 |
-
|
1588 |
-
# Add persistent git options.
|
1589 |
-
call.extend(self._persistent_git_options)
|
1590 |
-
|
1591 |
-
# Add the git options, then reset to empty to avoid side effects.
|
1592 |
-
call.extend(self._git_options)
|
1593 |
-
self._git_options = ()
|
1594 |
-
|
1595 |
-
call.append(dashify(method))
|
1596 |
-
call.extend(args_list)
|
1597 |
-
|
1598 |
-
return self.execute(call, **exec_kwargs)
|
1599 |
-
|
1600 |
-
def _parse_object_header(self, header_line: str) -> Tuple[str, str, int]:
|
1601 |
-
"""
|
1602 |
-
:param header_line:
|
1603 |
-
A line of the form::
|
1604 |
-
|
1605 |
-
<hex_sha> type_string size_as_int
|
1606 |
-
|
1607 |
-
:return:
|
1608 |
-
(hex_sha, type_string, size_as_int)
|
1609 |
-
|
1610 |
-
:raise ValueError:
|
1611 |
-
If the header contains indication for an error due to incorrect input sha.
|
1612 |
-
"""
|
1613 |
-
tokens = header_line.split()
|
1614 |
-
if len(tokens) != 3:
|
1615 |
-
if not tokens:
|
1616 |
-
err_msg = (
|
1617 |
-
f"SHA is empty, possible dubious ownership in the repository "
|
1618 |
-
f"""at {self._working_dir}.\n If this is unintended run:\n\n """
|
1619 |
-
f""" "git config --global --add safe.directory {self._working_dir}" """
|
1620 |
-
)
|
1621 |
-
raise ValueError(err_msg)
|
1622 |
-
else:
|
1623 |
-
raise ValueError("SHA %s could not be resolved, git returned: %r" % (tokens[0], header_line.strip()))
|
1624 |
-
# END handle actual return value
|
1625 |
-
# END error handling
|
1626 |
-
|
1627 |
-
if len(tokens[0]) != 40:
|
1628 |
-
raise ValueError("Failed to parse header: %r" % header_line)
|
1629 |
-
return (tokens[0], tokens[1], int(tokens[2]))
|
1630 |
-
|
1631 |
-
def _prepare_ref(self, ref: AnyStr) -> bytes:
|
1632 |
-
# Required for command to separate refs on stdin, as bytes.
|
1633 |
-
if isinstance(ref, bytes):
|
1634 |
-
# Assume 40 bytes hexsha - bin-to-ascii for some reason returns bytes, not text.
|
1635 |
-
refstr: str = ref.decode("ascii")
|
1636 |
-
elif not isinstance(ref, str):
|
1637 |
-
refstr = str(ref) # Could be ref-object.
|
1638 |
-
else:
|
1639 |
-
refstr = ref
|
1640 |
-
|
1641 |
-
if not refstr.endswith("\n"):
|
1642 |
-
refstr += "\n"
|
1643 |
-
return refstr.encode(defenc)
|
1644 |
-
|
1645 |
-
def _get_persistent_cmd(self, attr_name: str, cmd_name: str, *args: Any, **kwargs: Any) -> "Git.AutoInterrupt":
|
1646 |
-
cur_val = getattr(self, attr_name)
|
1647 |
-
if cur_val is not None:
|
1648 |
-
return cur_val
|
1649 |
-
|
1650 |
-
options = {"istream": PIPE, "as_process": True}
|
1651 |
-
options.update(kwargs)
|
1652 |
-
|
1653 |
-
cmd = self._call_process(cmd_name, *args, **options)
|
1654 |
-
setattr(self, attr_name, cmd)
|
1655 |
-
cmd = cast("Git.AutoInterrupt", cmd)
|
1656 |
-
return cmd
|
1657 |
-
|
1658 |
-
def __get_object_header(self, cmd: "Git.AutoInterrupt", ref: AnyStr) -> Tuple[str, str, int]:
|
1659 |
-
if cmd.stdin and cmd.stdout:
|
1660 |
-
cmd.stdin.write(self._prepare_ref(ref))
|
1661 |
-
cmd.stdin.flush()
|
1662 |
-
return self._parse_object_header(cmd.stdout.readline())
|
1663 |
-
else:
|
1664 |
-
raise ValueError("cmd stdin was empty")
|
1665 |
-
|
1666 |
-
def get_object_header(self, ref: str) -> Tuple[str, str, int]:
|
1667 |
-
"""Use this method to quickly examine the type and size of the object behind the
|
1668 |
-
given ref.
|
1669 |
-
|
1670 |
-
:note:
|
1671 |
-
The method will only suffer from the costs of command invocation once and
|
1672 |
-
reuses the command in subsequent calls.
|
1673 |
-
|
1674 |
-
:return:
|
1675 |
-
(hexsha, type_string, size_as_int)
|
1676 |
-
"""
|
1677 |
-
cmd = self._get_persistent_cmd("cat_file_header", "cat_file", batch_check=True)
|
1678 |
-
return self.__get_object_header(cmd, ref)
|
1679 |
-
|
1680 |
-
def get_object_data(self, ref: str) -> Tuple[str, str, int, bytes]:
|
1681 |
-
"""Similar to :meth:`get_object_header`, but returns object data as well.
|
1682 |
-
|
1683 |
-
:return:
|
1684 |
-
(hexsha, type_string, size_as_int, data_string)
|
1685 |
-
|
1686 |
-
:note:
|
1687 |
-
Not threadsafe.
|
1688 |
-
"""
|
1689 |
-
hexsha, typename, size, stream = self.stream_object_data(ref)
|
1690 |
-
data = stream.read(size)
|
1691 |
-
del stream
|
1692 |
-
return (hexsha, typename, size, data)
|
1693 |
-
|
1694 |
-
def stream_object_data(self, ref: str) -> Tuple[str, str, int, "Git.CatFileContentStream"]:
|
1695 |
-
"""Similar to :meth:`get_object_data`, but returns the data as a stream.
|
1696 |
-
|
1697 |
-
:return:
|
1698 |
-
(hexsha, type_string, size_as_int, stream)
|
1699 |
-
|
1700 |
-
:note:
|
1701 |
-
This method is not threadsafe. You need one independent :class:`Git`
|
1702 |
-
instance per thread to be safe!
|
1703 |
-
"""
|
1704 |
-
cmd = self._get_persistent_cmd("cat_file_all", "cat_file", batch=True)
|
1705 |
-
hexsha, typename, size = self.__get_object_header(cmd, ref)
|
1706 |
-
cmd_stdout = cmd.stdout if cmd.stdout is not None else io.BytesIO()
|
1707 |
-
return (hexsha, typename, size, self.CatFileContentStream(size, cmd_stdout))
|
1708 |
-
|
1709 |
-
def clear_cache(self) -> "Git":
|
1710 |
-
"""Clear all kinds of internal caches to release resources.
|
1711 |
-
|
1712 |
-
Currently persistent commands will be interrupted.
|
1713 |
-
|
1714 |
-
:return:
|
1715 |
-
self
|
1716 |
-
"""
|
1717 |
-
for cmd in (self.cat_file_all, self.cat_file_header):
|
1718 |
-
if cmd:
|
1719 |
-
cmd.__del__()
|
1720 |
-
|
1721 |
-
self.cat_file_all = None
|
1722 |
-
self.cat_file_header = None
|
1723 |
-
return self
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/compat.py
DELETED
@@ -1,165 +0,0 @@
|
|
1 |
-
# Copyright (C) 2008, 2009 Michael Trier ([email protected]) and contributors
|
2 |
-
#
|
3 |
-
# This module is part of GitPython and is released under the
|
4 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
5 |
-
|
6 |
-
"""Utilities to help provide compatibility with Python 3.
|
7 |
-
|
8 |
-
This module exists for historical reasons. Code outside GitPython may make use of public
|
9 |
-
members of this module, but is unlikely to benefit from doing so. GitPython continues to
|
10 |
-
use some of these utilities, in some cases for compatibility across different platforms.
|
11 |
-
"""
|
12 |
-
|
13 |
-
import locale
|
14 |
-
import os
|
15 |
-
import sys
|
16 |
-
import warnings
|
17 |
-
|
18 |
-
from gitdb.utils.encoding import force_bytes, force_text # noqa: F401
|
19 |
-
|
20 |
-
# typing --------------------------------------------------------------------
|
21 |
-
|
22 |
-
from typing import (
|
23 |
-
Any, # noqa: F401
|
24 |
-
AnyStr,
|
25 |
-
Dict, # noqa: F401
|
26 |
-
IO, # noqa: F401
|
27 |
-
List,
|
28 |
-
Optional,
|
29 |
-
TYPE_CHECKING,
|
30 |
-
Tuple, # noqa: F401
|
31 |
-
Type, # noqa: F401
|
32 |
-
Union,
|
33 |
-
overload,
|
34 |
-
)
|
35 |
-
|
36 |
-
# ---------------------------------------------------------------------------
|
37 |
-
|
38 |
-
|
39 |
-
_deprecated_platform_aliases = {
|
40 |
-
"is_win": os.name == "nt",
|
41 |
-
"is_posix": os.name == "posix",
|
42 |
-
"is_darwin": sys.platform == "darwin",
|
43 |
-
}
|
44 |
-
|
45 |
-
|
46 |
-
def _getattr(name: str) -> Any:
|
47 |
-
try:
|
48 |
-
value = _deprecated_platform_aliases[name]
|
49 |
-
except KeyError:
|
50 |
-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
|
51 |
-
|
52 |
-
warnings.warn(
|
53 |
-
f"{__name__}.{name} and other is_<platform> aliases are deprecated. "
|
54 |
-
"Write the desired os.name or sys.platform check explicitly instead.",
|
55 |
-
DeprecationWarning,
|
56 |
-
stacklevel=2,
|
57 |
-
)
|
58 |
-
return value
|
59 |
-
|
60 |
-
|
61 |
-
if not TYPE_CHECKING: # Preserve static checking for undefined/misspelled attributes.
|
62 |
-
__getattr__ = _getattr
|
63 |
-
|
64 |
-
|
65 |
-
def __dir__() -> List[str]:
|
66 |
-
return [*globals(), *_deprecated_platform_aliases]
|
67 |
-
|
68 |
-
|
69 |
-
is_win: bool
|
70 |
-
"""Deprecated alias for ``os.name == "nt"`` to check for native Windows.
|
71 |
-
|
72 |
-
This is deprecated because it is clearer to write out :attr:`os.name` or
|
73 |
-
:attr:`sys.platform` checks explicitly, especially in cases where it matters which is
|
74 |
-
used.
|
75 |
-
|
76 |
-
:note:
|
77 |
-
``is_win`` is ``False`` on Cygwin, but is often wrongly assumed ``True``. To detect
|
78 |
-
Cygwin, use ``sys.platform == "cygwin"``.
|
79 |
-
"""
|
80 |
-
|
81 |
-
is_posix: bool
|
82 |
-
"""Deprecated alias for ``os.name == "posix"`` to check for Unix-like ("POSIX") systems.
|
83 |
-
|
84 |
-
This is deprecated because it clearer to write out :attr:`os.name` or
|
85 |
-
:attr:`sys.platform` checks explicitly, especially in cases where it matters which is
|
86 |
-
used.
|
87 |
-
|
88 |
-
:note:
|
89 |
-
For POSIX systems, more detailed information is available in :attr:`sys.platform`,
|
90 |
-
while :attr:`os.name` is always ``"posix"`` on such systems, including macOS
|
91 |
-
(Darwin).
|
92 |
-
"""
|
93 |
-
|
94 |
-
is_darwin: bool
|
95 |
-
"""Deprecated alias for ``sys.platform == "darwin"`` to check for macOS (Darwin).
|
96 |
-
|
97 |
-
This is deprecated because it clearer to write out :attr:`os.name` or
|
98 |
-
:attr:`sys.platform` checks explicitly.
|
99 |
-
|
100 |
-
:note:
|
101 |
-
For macOS (Darwin), ``os.name == "posix"`` as in other Unix-like systems, while
|
102 |
-
``sys.platform == "darwin"``.
|
103 |
-
"""
|
104 |
-
|
105 |
-
defenc = sys.getfilesystemencoding()
|
106 |
-
"""The encoding used to convert between Unicode and bytes filenames."""
|
107 |
-
|
108 |
-
|
109 |
-
@overload
|
110 |
-
def safe_decode(s: None) -> None: ...
|
111 |
-
|
112 |
-
|
113 |
-
@overload
|
114 |
-
def safe_decode(s: AnyStr) -> str: ...
|
115 |
-
|
116 |
-
|
117 |
-
def safe_decode(s: Union[AnyStr, None]) -> Optional[str]:
|
118 |
-
"""Safely decode a binary string to Unicode."""
|
119 |
-
if isinstance(s, str):
|
120 |
-
return s
|
121 |
-
elif isinstance(s, bytes):
|
122 |
-
return s.decode(defenc, "surrogateescape")
|
123 |
-
elif s is None:
|
124 |
-
return None
|
125 |
-
else:
|
126 |
-
raise TypeError("Expected bytes or text, but got %r" % (s,))
|
127 |
-
|
128 |
-
|
129 |
-
@overload
|
130 |
-
def safe_encode(s: None) -> None: ...
|
131 |
-
|
132 |
-
|
133 |
-
@overload
|
134 |
-
def safe_encode(s: AnyStr) -> bytes: ...
|
135 |
-
|
136 |
-
|
137 |
-
def safe_encode(s: Optional[AnyStr]) -> Optional[bytes]:
|
138 |
-
"""Safely encode a binary string to Unicode."""
|
139 |
-
if isinstance(s, str):
|
140 |
-
return s.encode(defenc)
|
141 |
-
elif isinstance(s, bytes):
|
142 |
-
return s
|
143 |
-
elif s is None:
|
144 |
-
return None
|
145 |
-
else:
|
146 |
-
raise TypeError("Expected bytes or text, but got %r" % (s,))
|
147 |
-
|
148 |
-
|
149 |
-
@overload
|
150 |
-
def win_encode(s: None) -> None: ...
|
151 |
-
|
152 |
-
|
153 |
-
@overload
|
154 |
-
def win_encode(s: AnyStr) -> bytes: ...
|
155 |
-
|
156 |
-
|
157 |
-
def win_encode(s: Optional[AnyStr]) -> Optional[bytes]:
|
158 |
-
"""Encode Unicode strings for process arguments on Windows."""
|
159 |
-
if isinstance(s, str):
|
160 |
-
return s.encode(locale.getpreferredencoding(False))
|
161 |
-
elif isinstance(s, bytes):
|
162 |
-
return s
|
163 |
-
elif s is not None:
|
164 |
-
raise TypeError("Expected bytes or text, but got %r" % (s,))
|
165 |
-
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/config.py
DELETED
@@ -1,944 +0,0 @@
|
|
1 |
-
# Copyright (C) 2008, 2009 Michael Trier ([email protected]) and contributors
|
2 |
-
#
|
3 |
-
# This module is part of GitPython and is released under the
|
4 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
5 |
-
|
6 |
-
"""Parser for reading and writing configuration files."""
|
7 |
-
|
8 |
-
__all__ = ["GitConfigParser", "SectionConstraint"]
|
9 |
-
|
10 |
-
import abc
|
11 |
-
import configparser as cp
|
12 |
-
import fnmatch
|
13 |
-
from functools import wraps
|
14 |
-
import inspect
|
15 |
-
from io import BufferedReader, IOBase
|
16 |
-
import logging
|
17 |
-
import os
|
18 |
-
import os.path as osp
|
19 |
-
import re
|
20 |
-
import sys
|
21 |
-
|
22 |
-
from git.compat import defenc, force_text
|
23 |
-
from git.util import LockFile
|
24 |
-
|
25 |
-
# typing-------------------------------------------------------
|
26 |
-
|
27 |
-
from typing import (
|
28 |
-
Any,
|
29 |
-
Callable,
|
30 |
-
Generic,
|
31 |
-
IO,
|
32 |
-
List,
|
33 |
-
Dict,
|
34 |
-
Sequence,
|
35 |
-
TYPE_CHECKING,
|
36 |
-
Tuple,
|
37 |
-
TypeVar,
|
38 |
-
Union,
|
39 |
-
cast,
|
40 |
-
)
|
41 |
-
|
42 |
-
from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, assert_never, _T
|
43 |
-
|
44 |
-
if TYPE_CHECKING:
|
45 |
-
from io import BytesIO
|
46 |
-
|
47 |
-
from git.repo.base import Repo
|
48 |
-
|
49 |
-
T_ConfigParser = TypeVar("T_ConfigParser", bound="GitConfigParser")
|
50 |
-
T_OMD_value = TypeVar("T_OMD_value", str, bytes, int, float, bool)
|
51 |
-
|
52 |
-
if sys.version_info[:3] < (3, 7, 2):
|
53 |
-
# typing.Ordereddict not added until Python 3.7.2.
|
54 |
-
from collections import OrderedDict
|
55 |
-
|
56 |
-
OrderedDict_OMD = OrderedDict
|
57 |
-
else:
|
58 |
-
from typing import OrderedDict
|
59 |
-
|
60 |
-
OrderedDict_OMD = OrderedDict[str, List[T_OMD_value]] # type: ignore[assignment, misc]
|
61 |
-
|
62 |
-
# -------------------------------------------------------------
|
63 |
-
|
64 |
-
_logger = logging.getLogger(__name__)
|
65 |
-
|
66 |
-
CONFIG_LEVELS: ConfigLevels_Tup = ("system", "user", "global", "repository")
|
67 |
-
"""The configuration level of a configuration file."""
|
68 |
-
|
69 |
-
CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch):(.+)\"")
|
70 |
-
"""Section pattern to detect conditional includes.
|
71 |
-
|
72 |
-
See: https://git-scm.com/docs/git-config#_conditional_includes
|
73 |
-
"""
|
74 |
-
|
75 |
-
|
76 |
-
class MetaParserBuilder(abc.ABCMeta): # noqa: B024
|
77 |
-
"""Utility class wrapping base-class methods into decorators that assure read-only
|
78 |
-
properties."""
|
79 |
-
|
80 |
-
def __new__(cls, name: str, bases: Tuple, clsdict: Dict[str, Any]) -> "MetaParserBuilder":
|
81 |
-
"""Equip all base-class methods with a needs_values decorator, and all non-const
|
82 |
-
methods with a :func:`set_dirty_and_flush_changes` decorator in addition to
|
83 |
-
that.
|
84 |
-
"""
|
85 |
-
kmm = "_mutating_methods_"
|
86 |
-
if kmm in clsdict:
|
87 |
-
mutating_methods = clsdict[kmm]
|
88 |
-
for base in bases:
|
89 |
-
methods = (t for t in inspect.getmembers(base, inspect.isroutine) if not t[0].startswith("_"))
|
90 |
-
for name, method in methods:
|
91 |
-
if name in clsdict:
|
92 |
-
continue
|
93 |
-
method_with_values = needs_values(method)
|
94 |
-
if name in mutating_methods:
|
95 |
-
method_with_values = set_dirty_and_flush_changes(method_with_values)
|
96 |
-
# END mutating methods handling
|
97 |
-
|
98 |
-
clsdict[name] = method_with_values
|
99 |
-
# END for each name/method pair
|
100 |
-
# END for each base
|
101 |
-
# END if mutating methods configuration is set
|
102 |
-
|
103 |
-
new_type = super().__new__(cls, name, bases, clsdict)
|
104 |
-
return new_type
|
105 |
-
|
106 |
-
|
107 |
-
def needs_values(func: Callable[..., _T]) -> Callable[..., _T]:
|
108 |
-
"""Return a method for ensuring we read values (on demand) before we try to access
|
109 |
-
them."""
|
110 |
-
|
111 |
-
@wraps(func)
|
112 |
-
def assure_data_present(self: "GitConfigParser", *args: Any, **kwargs: Any) -> _T:
|
113 |
-
self.read()
|
114 |
-
return func(self, *args, **kwargs)
|
115 |
-
|
116 |
-
# END wrapper method
|
117 |
-
return assure_data_present
|
118 |
-
|
119 |
-
|
120 |
-
def set_dirty_and_flush_changes(non_const_func: Callable[..., _T]) -> Callable[..., _T]:
|
121 |
-
"""Return a method that checks whether given non constant function may be called.
|
122 |
-
|
123 |
-
If so, the instance will be set dirty. Additionally, we flush the changes right to
|
124 |
-
disk.
|
125 |
-
"""
|
126 |
-
|
127 |
-
def flush_changes(self: "GitConfigParser", *args: Any, **kwargs: Any) -> _T:
|
128 |
-
rval = non_const_func(self, *args, **kwargs)
|
129 |
-
self._dirty = True
|
130 |
-
self.write()
|
131 |
-
return rval
|
132 |
-
|
133 |
-
# END wrapper method
|
134 |
-
flush_changes.__name__ = non_const_func.__name__
|
135 |
-
return flush_changes
|
136 |
-
|
137 |
-
|
138 |
-
class SectionConstraint(Generic[T_ConfigParser]):
|
139 |
-
"""Constrains a ConfigParser to only option commands which are constrained to
|
140 |
-
always use the section we have been initialized with.
|
141 |
-
|
142 |
-
It supports all ConfigParser methods that operate on an option.
|
143 |
-
|
144 |
-
:note:
|
145 |
-
If used as a context manager, will release the wrapped ConfigParser.
|
146 |
-
"""
|
147 |
-
|
148 |
-
__slots__ = ("_config", "_section_name")
|
149 |
-
|
150 |
-
_valid_attrs_ = (
|
151 |
-
"get_value",
|
152 |
-
"set_value",
|
153 |
-
"get",
|
154 |
-
"set",
|
155 |
-
"getint",
|
156 |
-
"getfloat",
|
157 |
-
"getboolean",
|
158 |
-
"has_option",
|
159 |
-
"remove_section",
|
160 |
-
"remove_option",
|
161 |
-
"options",
|
162 |
-
)
|
163 |
-
|
164 |
-
def __init__(self, config: T_ConfigParser, section: str) -> None:
|
165 |
-
self._config = config
|
166 |
-
self._section_name = section
|
167 |
-
|
168 |
-
def __del__(self) -> None:
|
169 |
-
# Yes, for some reason, we have to call it explicitly for it to work in PY3 !
|
170 |
-
# Apparently __del__ doesn't get call anymore if refcount becomes 0
|
171 |
-
# Ridiculous ... .
|
172 |
-
self._config.release()
|
173 |
-
|
174 |
-
def __getattr__(self, attr: str) -> Any:
|
175 |
-
if attr in self._valid_attrs_:
|
176 |
-
return lambda *args, **kwargs: self._call_config(attr, *args, **kwargs)
|
177 |
-
return super().__getattribute__(attr)
|
178 |
-
|
179 |
-
def _call_config(self, method: str, *args: Any, **kwargs: Any) -> Any:
|
180 |
-
"""Call the configuration at the given method which must take a section name as
|
181 |
-
first argument."""
|
182 |
-
return getattr(self._config, method)(self._section_name, *args, **kwargs)
|
183 |
-
|
184 |
-
@property
|
185 |
-
def config(self) -> T_ConfigParser:
|
186 |
-
"""return: ConfigParser instance we constrain"""
|
187 |
-
return self._config
|
188 |
-
|
189 |
-
def release(self) -> None:
|
190 |
-
"""Equivalent to :meth:`GitConfigParser.release`, which is called on our
|
191 |
-
underlying parser instance."""
|
192 |
-
return self._config.release()
|
193 |
-
|
194 |
-
def __enter__(self) -> "SectionConstraint[T_ConfigParser]":
|
195 |
-
self._config.__enter__()
|
196 |
-
return self
|
197 |
-
|
198 |
-
def __exit__(self, exception_type: str, exception_value: str, traceback: str) -> None:
|
199 |
-
self._config.__exit__(exception_type, exception_value, traceback)
|
200 |
-
|
201 |
-
|
202 |
-
class _OMD(OrderedDict_OMD):
|
203 |
-
"""Ordered multi-dict."""
|
204 |
-
|
205 |
-
def __setitem__(self, key: str, value: _T) -> None:
|
206 |
-
super().__setitem__(key, [value])
|
207 |
-
|
208 |
-
def add(self, key: str, value: Any) -> None:
|
209 |
-
if key not in self:
|
210 |
-
super().__setitem__(key, [value])
|
211 |
-
return
|
212 |
-
|
213 |
-
super().__getitem__(key).append(value)
|
214 |
-
|
215 |
-
def setall(self, key: str, values: List[_T]) -> None:
|
216 |
-
super().__setitem__(key, values)
|
217 |
-
|
218 |
-
def __getitem__(self, key: str) -> Any:
|
219 |
-
return super().__getitem__(key)[-1]
|
220 |
-
|
221 |
-
def getlast(self, key: str) -> Any:
|
222 |
-
return super().__getitem__(key)[-1]
|
223 |
-
|
224 |
-
def setlast(self, key: str, value: Any) -> None:
|
225 |
-
if key not in self:
|
226 |
-
super().__setitem__(key, [value])
|
227 |
-
return
|
228 |
-
|
229 |
-
prior = super().__getitem__(key)
|
230 |
-
prior[-1] = value
|
231 |
-
|
232 |
-
def get(self, key: str, default: Union[_T, None] = None) -> Union[_T, None]:
|
233 |
-
return super().get(key, [default])[-1]
|
234 |
-
|
235 |
-
def getall(self, key: str) -> List[_T]:
|
236 |
-
return super().__getitem__(key)
|
237 |
-
|
238 |
-
def items(self) -> List[Tuple[str, _T]]: # type: ignore[override]
|
239 |
-
"""List of (key, last value for key)."""
|
240 |
-
return [(k, self[k]) for k in self]
|
241 |
-
|
242 |
-
def items_all(self) -> List[Tuple[str, List[_T]]]:
|
243 |
-
"""List of (key, list of values for key)."""
|
244 |
-
return [(k, self.getall(k)) for k in self]
|
245 |
-
|
246 |
-
|
247 |
-
def get_config_path(config_level: Lit_config_levels) -> str:
|
248 |
-
# We do not support an absolute path of the gitconfig on Windows.
|
249 |
-
# Use the global config instead.
|
250 |
-
if sys.platform == "win32" and config_level == "system":
|
251 |
-
config_level = "global"
|
252 |
-
|
253 |
-
if config_level == "system":
|
254 |
-
return "/etc/gitconfig"
|
255 |
-
elif config_level == "user":
|
256 |
-
config_home = os.environ.get("XDG_CONFIG_HOME") or osp.join(os.environ.get("HOME", "~"), ".config")
|
257 |
-
return osp.normpath(osp.expanduser(osp.join(config_home, "git", "config")))
|
258 |
-
elif config_level == "global":
|
259 |
-
return osp.normpath(osp.expanduser("~/.gitconfig"))
|
260 |
-
elif config_level == "repository":
|
261 |
-
raise ValueError("No repo to get repository configuration from. Use Repo._get_config_path")
|
262 |
-
else:
|
263 |
-
# Should not reach here. Will raise ValueError if does. Static typing will warn
|
264 |
-
# about missing elifs.
|
265 |
-
assert_never( # type: ignore[unreachable]
|
266 |
-
config_level,
|
267 |
-
ValueError(f"Invalid configuration level: {config_level!r}"),
|
268 |
-
)
|
269 |
-
|
270 |
-
|
271 |
-
class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
272 |
-
"""Implements specifics required to read git style configuration files.
|
273 |
-
|
274 |
-
This variation behaves much like the :manpage:`git-config(1)` command, such that the
|
275 |
-
configuration will be read on demand based on the filepath given during
|
276 |
-
initialization.
|
277 |
-
|
278 |
-
The changes will automatically be written once the instance goes out of scope, but
|
279 |
-
can be triggered manually as well.
|
280 |
-
|
281 |
-
The configuration file will be locked if you intend to change values preventing
|
282 |
-
other instances to write concurrently.
|
283 |
-
|
284 |
-
:note:
|
285 |
-
The config is case-sensitive even when queried, hence section and option names
|
286 |
-
must match perfectly.
|
287 |
-
|
288 |
-
:note:
|
289 |
-
If used as a context manager, this will release the locked file.
|
290 |
-
"""
|
291 |
-
|
292 |
-
# { Configuration
|
293 |
-
t_lock = LockFile
|
294 |
-
"""The lock type determines the type of lock to use in new configuration readers.
|
295 |
-
|
296 |
-
They must be compatible to the :class:`~git.util.LockFile` interface.
|
297 |
-
A suitable alternative would be the :class:`~git.util.BlockingLockFile`.
|
298 |
-
"""
|
299 |
-
|
300 |
-
re_comment = re.compile(r"^\s*[#;]")
|
301 |
-
# } END configuration
|
302 |
-
|
303 |
-
optvalueonly_source = r"\s*(?P<option>[^:=\s][^:=]*)"
|
304 |
-
|
305 |
-
OPTVALUEONLY = re.compile(optvalueonly_source)
|
306 |
-
|
307 |
-
OPTCRE = re.compile(optvalueonly_source + r"\s*(?P<vi>[:=])\s*" + r"(?P<value>.*)$")
|
308 |
-
|
309 |
-
del optvalueonly_source
|
310 |
-
|
311 |
-
_mutating_methods_ = ("add_section", "remove_section", "remove_option", "set")
|
312 |
-
"""Names of :class:`~configparser.RawConfigParser` methods able to change the
|
313 |
-
instance."""
|
314 |
-
|
315 |
-
def __init__(
|
316 |
-
self,
|
317 |
-
file_or_files: Union[None, PathLike, "BytesIO", Sequence[Union[PathLike, "BytesIO"]]] = None,
|
318 |
-
read_only: bool = True,
|
319 |
-
merge_includes: bool = True,
|
320 |
-
config_level: Union[Lit_config_levels, None] = None,
|
321 |
-
repo: Union["Repo", None] = None,
|
322 |
-
) -> None:
|
323 |
-
"""Initialize a configuration reader to read the given `file_or_files` and to
|
324 |
-
possibly allow changes to it by setting `read_only` False.
|
325 |
-
|
326 |
-
:param file_or_files:
|
327 |
-
A file path or file object, or a sequence of possibly more than one of them.
|
328 |
-
|
329 |
-
:param read_only:
|
330 |
-
If ``True``, the ConfigParser may only read the data, but not change it.
|
331 |
-
If ``False``, only a single file path or file object may be given. We will
|
332 |
-
write back the changes when they happen, or when the ConfigParser is
|
333 |
-
released. This will not happen if other configuration files have been
|
334 |
-
included.
|
335 |
-
|
336 |
-
:param merge_includes:
|
337 |
-
If ``True``, we will read files mentioned in ``[include]`` sections and
|
338 |
-
merge their contents into ours. This makes it impossible to write back an
|
339 |
-
individual configuration file. Thus, if you want to modify a single
|
340 |
-
configuration file, turn this off to leave the original dataset unaltered
|
341 |
-
when reading it.
|
342 |
-
|
343 |
-
:param repo:
|
344 |
-
Reference to repository to use if ``[includeIf]`` sections are found in
|
345 |
-
configuration files.
|
346 |
-
"""
|
347 |
-
cp.RawConfigParser.__init__(self, dict_type=_OMD)
|
348 |
-
self._dict: Callable[..., _OMD]
|
349 |
-
self._defaults: _OMD
|
350 |
-
self._sections: _OMD
|
351 |
-
|
352 |
-
# Used in Python 3. Needs to stay in sync with sections for underlying
|
353 |
-
# implementation to work.
|
354 |
-
if not hasattr(self, "_proxies"):
|
355 |
-
self._proxies = self._dict()
|
356 |
-
|
357 |
-
if file_or_files is not None:
|
358 |
-
self._file_or_files: Union[PathLike, "BytesIO", Sequence[Union[PathLike, "BytesIO"]]] = file_or_files
|
359 |
-
else:
|
360 |
-
if config_level is None:
|
361 |
-
if read_only:
|
362 |
-
self._file_or_files = [
|
363 |
-
get_config_path(cast(Lit_config_levels, f)) for f in CONFIG_LEVELS if f != "repository"
|
364 |
-
]
|
365 |
-
else:
|
366 |
-
raise ValueError("No configuration level or configuration files specified")
|
367 |
-
else:
|
368 |
-
self._file_or_files = [get_config_path(config_level)]
|
369 |
-
|
370 |
-
self._read_only = read_only
|
371 |
-
self._dirty = False
|
372 |
-
self._is_initialized = False
|
373 |
-
self._merge_includes = merge_includes
|
374 |
-
self._repo = repo
|
375 |
-
self._lock: Union["LockFile", None] = None
|
376 |
-
self._acquire_lock()
|
377 |
-
|
378 |
-
def _acquire_lock(self) -> None:
|
379 |
-
if not self._read_only:
|
380 |
-
if not self._lock:
|
381 |
-
if isinstance(self._file_or_files, (str, os.PathLike)):
|
382 |
-
file_or_files = self._file_or_files
|
383 |
-
elif isinstance(self._file_or_files, (tuple, list, Sequence)):
|
384 |
-
raise ValueError(
|
385 |
-
"Write-ConfigParsers can operate on a single file only, multiple files have been passed"
|
386 |
-
)
|
387 |
-
else:
|
388 |
-
file_or_files = self._file_or_files.name
|
389 |
-
|
390 |
-
# END get filename from handle/stream
|
391 |
-
# Initialize lock base - we want to write.
|
392 |
-
self._lock = self.t_lock(file_or_files)
|
393 |
-
# END lock check
|
394 |
-
|
395 |
-
self._lock._obtain_lock()
|
396 |
-
# END read-only check
|
397 |
-
|
398 |
-
def __del__(self) -> None:
|
399 |
-
"""Write pending changes if required and release locks."""
|
400 |
-
# NOTE: Only consistent in Python 2.
|
401 |
-
self.release()
|
402 |
-
|
403 |
-
def __enter__(self) -> "GitConfigParser":
|
404 |
-
self._acquire_lock()
|
405 |
-
return self
|
406 |
-
|
407 |
-
def __exit__(self, *args: Any) -> None:
|
408 |
-
self.release()
|
409 |
-
|
410 |
-
def release(self) -> None:
|
411 |
-
"""Flush changes and release the configuration write lock. This instance must
|
412 |
-
not be used anymore afterwards.
|
413 |
-
|
414 |
-
In Python 3, it's required to explicitly release locks and flush changes, as
|
415 |
-
``__del__`` is not called deterministically anymore.
|
416 |
-
"""
|
417 |
-
# Checking for the lock here makes sure we do not raise during write()
|
418 |
-
# in case an invalid parser was created who could not get a lock.
|
419 |
-
if self.read_only or (self._lock and not self._lock._has_lock()):
|
420 |
-
return
|
421 |
-
|
422 |
-
try:
|
423 |
-
self.write()
|
424 |
-
except IOError:
|
425 |
-
_logger.error("Exception during destruction of GitConfigParser", exc_info=True)
|
426 |
-
except ReferenceError:
|
427 |
-
# This happens in Python 3... and usually means that some state cannot be
|
428 |
-
# written as the sections dict cannot be iterated. This usually happens when
|
429 |
-
# the interpreter is shutting down. Can it be fixed?
|
430 |
-
pass
|
431 |
-
finally:
|
432 |
-
if self._lock is not None:
|
433 |
-
self._lock._release_lock()
|
434 |
-
|
435 |
-
def optionxform(self, optionstr: str) -> str:
|
436 |
-
"""Do not transform options in any way when writing."""
|
437 |
-
return optionstr
|
438 |
-
|
439 |
-
def _read(self, fp: Union[BufferedReader, IO[bytes]], fpname: str) -> None:
|
440 |
-
"""Originally a direct copy of the Python 2.4 version of
|
441 |
-
:meth:`RawConfigParser._read <configparser.RawConfigParser._read>`, to ensure it
|
442 |
-
uses ordered dicts.
|
443 |
-
|
444 |
-
The ordering bug was fixed in Python 2.4, and dict itself keeps ordering since
|
445 |
-
Python 3.7. This has some other changes, especially that it ignores initial
|
446 |
-
whitespace, since git uses tabs. (Big comments are removed to be more compact.)
|
447 |
-
"""
|
448 |
-
cursect = None # None, or a dictionary.
|
449 |
-
optname = None
|
450 |
-
lineno = 0
|
451 |
-
is_multi_line = False
|
452 |
-
e = None # None, or an exception.
|
453 |
-
|
454 |
-
def string_decode(v: str) -> str:
|
455 |
-
if v[-1] == "\\":
|
456 |
-
v = v[:-1]
|
457 |
-
# END cut trailing escapes to prevent decode error
|
458 |
-
|
459 |
-
return v.encode(defenc).decode("unicode_escape")
|
460 |
-
|
461 |
-
# END string_decode
|
462 |
-
|
463 |
-
while True:
|
464 |
-
# We assume to read binary!
|
465 |
-
line = fp.readline().decode(defenc)
|
466 |
-
if not line:
|
467 |
-
break
|
468 |
-
lineno = lineno + 1
|
469 |
-
# Comment or blank line?
|
470 |
-
if line.strip() == "" or self.re_comment.match(line):
|
471 |
-
continue
|
472 |
-
if line.split(None, 1)[0].lower() == "rem" and line[0] in "rR":
|
473 |
-
# No leading whitespace.
|
474 |
-
continue
|
475 |
-
|
476 |
-
# Is it a section header?
|
477 |
-
mo = self.SECTCRE.match(line.strip())
|
478 |
-
if not is_multi_line and mo:
|
479 |
-
sectname: str = mo.group("header").strip()
|
480 |
-
if sectname in self._sections:
|
481 |
-
cursect = self._sections[sectname]
|
482 |
-
elif sectname == cp.DEFAULTSECT:
|
483 |
-
cursect = self._defaults
|
484 |
-
else:
|
485 |
-
cursect = self._dict((("__name__", sectname),))
|
486 |
-
self._sections[sectname] = cursect
|
487 |
-
self._proxies[sectname] = None
|
488 |
-
# So sections can't start with a continuation line.
|
489 |
-
optname = None
|
490 |
-
# No section header in the file?
|
491 |
-
elif cursect is None:
|
492 |
-
raise cp.MissingSectionHeaderError(fpname, lineno, line)
|
493 |
-
# An option line?
|
494 |
-
elif not is_multi_line:
|
495 |
-
mo = self.OPTCRE.match(line)
|
496 |
-
if mo:
|
497 |
-
# We might just have handled the last line, which could contain a quotation we want to remove.
|
498 |
-
optname, vi, optval = mo.group("option", "vi", "value")
|
499 |
-
if vi in ("=", ":") and ";" in optval and not optval.strip().startswith('"'):
|
500 |
-
pos = optval.find(";")
|
501 |
-
if pos != -1 and optval[pos - 1].isspace():
|
502 |
-
optval = optval[:pos]
|
503 |
-
optval = optval.strip()
|
504 |
-
if optval == '""':
|
505 |
-
optval = ""
|
506 |
-
# END handle empty string
|
507 |
-
optname = self.optionxform(optname.rstrip())
|
508 |
-
if len(optval) > 1 and optval[0] == '"' and optval[-1] != '"':
|
509 |
-
is_multi_line = True
|
510 |
-
optval = string_decode(optval[1:])
|
511 |
-
# END handle multi-line
|
512 |
-
# Preserves multiple values for duplicate optnames.
|
513 |
-
cursect.add(optname, optval)
|
514 |
-
else:
|
515 |
-
# Check if it's an option with no value - it's just ignored by git.
|
516 |
-
if not self.OPTVALUEONLY.match(line):
|
517 |
-
if not e:
|
518 |
-
e = cp.ParsingError(fpname)
|
519 |
-
e.append(lineno, repr(line))
|
520 |
-
continue
|
521 |
-
else:
|
522 |
-
line = line.rstrip()
|
523 |
-
if line.endswith('"'):
|
524 |
-
is_multi_line = False
|
525 |
-
line = line[:-1]
|
526 |
-
# END handle quotations
|
527 |
-
optval = cursect.getlast(optname)
|
528 |
-
cursect.setlast(optname, optval + string_decode(line))
|
529 |
-
# END parse section or option
|
530 |
-
# END while reading
|
531 |
-
|
532 |
-
# If any parsing errors occurred, raise an exception.
|
533 |
-
if e:
|
534 |
-
raise e
|
535 |
-
|
536 |
-
def _has_includes(self) -> Union[bool, int]:
|
537 |
-
return self._merge_includes and len(self._included_paths())
|
538 |
-
|
539 |
-
def _included_paths(self) -> List[Tuple[str, str]]:
|
540 |
-
"""List all paths that must be included to configuration.
|
541 |
-
|
542 |
-
:return:
|
543 |
-
The list of paths, where each path is a tuple of (option, value).
|
544 |
-
"""
|
545 |
-
paths = []
|
546 |
-
|
547 |
-
for section in self.sections():
|
548 |
-
if section == "include":
|
549 |
-
paths += self.items(section)
|
550 |
-
|
551 |
-
match = CONDITIONAL_INCLUDE_REGEXP.search(section)
|
552 |
-
if match is None or self._repo is None:
|
553 |
-
continue
|
554 |
-
|
555 |
-
keyword = match.group(1)
|
556 |
-
value = match.group(2).strip()
|
557 |
-
|
558 |
-
if keyword in ["gitdir", "gitdir/i"]:
|
559 |
-
value = osp.expanduser(value)
|
560 |
-
|
561 |
-
if not any(value.startswith(s) for s in ["./", "/"]):
|
562 |
-
value = "**/" + value
|
563 |
-
if value.endswith("/"):
|
564 |
-
value += "**"
|
565 |
-
|
566 |
-
# Ensure that glob is always case insensitive if required.
|
567 |
-
if keyword.endswith("/i"):
|
568 |
-
value = re.sub(
|
569 |
-
r"[a-zA-Z]",
|
570 |
-
lambda m: "[{}{}]".format(m.group().lower(), m.group().upper()),
|
571 |
-
value,
|
572 |
-
)
|
573 |
-
if self._repo.git_dir:
|
574 |
-
if fnmatch.fnmatchcase(str(self._repo.git_dir), value):
|
575 |
-
paths += self.items(section)
|
576 |
-
|
577 |
-
elif keyword == "onbranch":
|
578 |
-
try:
|
579 |
-
branch_name = self._repo.active_branch.name
|
580 |
-
except TypeError:
|
581 |
-
# Ignore section if active branch cannot be retrieved.
|
582 |
-
continue
|
583 |
-
|
584 |
-
if fnmatch.fnmatchcase(branch_name, value):
|
585 |
-
paths += self.items(section)
|
586 |
-
|
587 |
-
return paths
|
588 |
-
|
589 |
-
def read(self) -> None: # type: ignore[override]
|
590 |
-
"""Read the data stored in the files we have been initialized with.
|
591 |
-
|
592 |
-
This will ignore files that cannot be read, possibly leaving an empty
|
593 |
-
configuration.
|
594 |
-
|
595 |
-
:raise IOError:
|
596 |
-
If a file cannot be handled.
|
597 |
-
"""
|
598 |
-
if self._is_initialized:
|
599 |
-
return
|
600 |
-
self._is_initialized = True
|
601 |
-
|
602 |
-
files_to_read: List[Union[PathLike, IO]] = [""]
|
603 |
-
if isinstance(self._file_or_files, (str, os.PathLike)):
|
604 |
-
# For str or Path, as str is a type of Sequence.
|
605 |
-
files_to_read = [self._file_or_files]
|
606 |
-
elif not isinstance(self._file_or_files, (tuple, list, Sequence)):
|
607 |
-
# Could merge with above isinstance once runtime type known.
|
608 |
-
files_to_read = [self._file_or_files]
|
609 |
-
else: # For lists or tuples.
|
610 |
-
files_to_read = list(self._file_or_files)
|
611 |
-
# END ensure we have a copy of the paths to handle
|
612 |
-
|
613 |
-
seen = set(files_to_read)
|
614 |
-
num_read_include_files = 0
|
615 |
-
while files_to_read:
|
616 |
-
file_path = files_to_read.pop(0)
|
617 |
-
file_ok = False
|
618 |
-
|
619 |
-
if hasattr(file_path, "seek"):
|
620 |
-
# Must be a file-object.
|
621 |
-
# TODO: Replace cast with assert to narrow type, once sure.
|
622 |
-
file_path = cast(IO[bytes], file_path)
|
623 |
-
self._read(file_path, file_path.name)
|
624 |
-
else:
|
625 |
-
# Assume a path if it is not a file-object.
|
626 |
-
file_path = cast(PathLike, file_path)
|
627 |
-
try:
|
628 |
-
with open(file_path, "rb") as fp:
|
629 |
-
file_ok = True
|
630 |
-
self._read(fp, fp.name)
|
631 |
-
except IOError:
|
632 |
-
continue
|
633 |
-
|
634 |
-
# Read includes and append those that we didn't handle yet. We expect all
|
635 |
-
# paths to be normalized and absolute (and will ensure that is the case).
|
636 |
-
if self._has_includes():
|
637 |
-
for _, include_path in self._included_paths():
|
638 |
-
if include_path.startswith("~"):
|
639 |
-
include_path = osp.expanduser(include_path)
|
640 |
-
if not osp.isabs(include_path):
|
641 |
-
if not file_ok:
|
642 |
-
continue
|
643 |
-
# END ignore relative paths if we don't know the configuration file path
|
644 |
-
file_path = cast(PathLike, file_path)
|
645 |
-
assert osp.isabs(file_path), "Need absolute paths to be sure our cycle checks will work"
|
646 |
-
include_path = osp.join(osp.dirname(file_path), include_path)
|
647 |
-
# END make include path absolute
|
648 |
-
include_path = osp.normpath(include_path)
|
649 |
-
if include_path in seen or not os.access(include_path, os.R_OK):
|
650 |
-
continue
|
651 |
-
seen.add(include_path)
|
652 |
-
# Insert included file to the top to be considered first.
|
653 |
-
files_to_read.insert(0, include_path)
|
654 |
-
num_read_include_files += 1
|
655 |
-
# END each include path in configuration file
|
656 |
-
# END handle includes
|
657 |
-
# END for each file object to read
|
658 |
-
|
659 |
-
# If there was no file included, we can safely write back (potentially) the
|
660 |
-
# configuration file without altering its meaning.
|
661 |
-
if num_read_include_files == 0:
|
662 |
-
self._merge_includes = False
|
663 |
-
|
664 |
-
def _write(self, fp: IO) -> None:
|
665 |
-
"""Write an .ini-format representation of the configuration state in
|
666 |
-
git compatible format."""
|
667 |
-
|
668 |
-
def write_section(name: str, section_dict: _OMD) -> None:
|
669 |
-
fp.write(("[%s]\n" % name).encode(defenc))
|
670 |
-
|
671 |
-
values: Sequence[str] # Runtime only gets str in tests, but should be whatever _OMD stores.
|
672 |
-
v: str
|
673 |
-
for key, values in section_dict.items_all():
|
674 |
-
if key == "__name__":
|
675 |
-
continue
|
676 |
-
|
677 |
-
for v in values:
|
678 |
-
fp.write(("\t%s = %s\n" % (key, self._value_to_string(v).replace("\n", "\n\t"))).encode(defenc))
|
679 |
-
# END if key is not __name__
|
680 |
-
|
681 |
-
# END section writing
|
682 |
-
|
683 |
-
if self._defaults:
|
684 |
-
write_section(cp.DEFAULTSECT, self._defaults)
|
685 |
-
value: _OMD
|
686 |
-
|
687 |
-
for name, value in self._sections.items():
|
688 |
-
write_section(name, value)
|
689 |
-
|
690 |
-
def items(self, section_name: str) -> List[Tuple[str, str]]: # type: ignore[override]
|
691 |
-
""":return: list((option, value), ...) pairs of all items in the given section"""
|
692 |
-
return [(k, v) for k, v in super().items(section_name) if k != "__name__"]
|
693 |
-
|
694 |
-
def items_all(self, section_name: str) -> List[Tuple[str, List[str]]]:
|
695 |
-
""":return: list((option, [values...]), ...) pairs of all items in the given section"""
|
696 |
-
rv = _OMD(self._defaults)
|
697 |
-
|
698 |
-
for k, vs in self._sections[section_name].items_all():
|
699 |
-
if k == "__name__":
|
700 |
-
continue
|
701 |
-
|
702 |
-
if k in rv and rv.getall(k) == vs:
|
703 |
-
continue
|
704 |
-
|
705 |
-
for v in vs:
|
706 |
-
rv.add(k, v)
|
707 |
-
|
708 |
-
return rv.items_all()
|
709 |
-
|
710 |
-
@needs_values
|
711 |
-
def write(self) -> None:
|
712 |
-
"""Write changes to our file, if there are changes at all.
|
713 |
-
|
714 |
-
:raise IOError:
|
715 |
-
If this is a read-only writer instance or if we could not obtain a file
|
716 |
-
lock.
|
717 |
-
"""
|
718 |
-
self._assure_writable("write")
|
719 |
-
if not self._dirty:
|
720 |
-
return
|
721 |
-
|
722 |
-
if isinstance(self._file_or_files, (list, tuple)):
|
723 |
-
raise AssertionError(
|
724 |
-
"Cannot write back if there is not exactly a single file to write to, have %i files"
|
725 |
-
% len(self._file_or_files)
|
726 |
-
)
|
727 |
-
# END assert multiple files
|
728 |
-
|
729 |
-
if self._has_includes():
|
730 |
-
_logger.debug(
|
731 |
-
"Skipping write-back of configuration file as include files were merged in."
|
732 |
-
+ "Set merge_includes=False to prevent this."
|
733 |
-
)
|
734 |
-
return
|
735 |
-
# END stop if we have include files
|
736 |
-
|
737 |
-
fp = self._file_or_files
|
738 |
-
|
739 |
-
# We have a physical file on disk, so get a lock.
|
740 |
-
is_file_lock = isinstance(fp, (str, os.PathLike, IOBase)) # TODO: Use PathLike (having dropped 3.5).
|
741 |
-
if is_file_lock and self._lock is not None: # Else raise error?
|
742 |
-
self._lock._obtain_lock()
|
743 |
-
|
744 |
-
if not hasattr(fp, "seek"):
|
745 |
-
fp = cast(PathLike, fp)
|
746 |
-
with open(fp, "wb") as fp_open:
|
747 |
-
self._write(fp_open)
|
748 |
-
else:
|
749 |
-
fp = cast("BytesIO", fp)
|
750 |
-
fp.seek(0)
|
751 |
-
# Make sure we do not overwrite into an existing file.
|
752 |
-
if hasattr(fp, "truncate"):
|
753 |
-
fp.truncate()
|
754 |
-
self._write(fp)
|
755 |
-
|
756 |
-
def _assure_writable(self, method_name: str) -> None:
|
757 |
-
if self.read_only:
|
758 |
-
raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name))
|
759 |
-
|
760 |
-
def add_section(self, section: str) -> None:
|
761 |
-
"""Assures added options will stay in order."""
|
762 |
-
return super().add_section(section)
|
763 |
-
|
764 |
-
@property
|
765 |
-
def read_only(self) -> bool:
|
766 |
-
""":return: ``True`` if this instance may change the configuration file"""
|
767 |
-
return self._read_only
|
768 |
-
|
769 |
-
# FIXME: Figure out if default or return type can really include bool.
|
770 |
-
def get_value(
|
771 |
-
self,
|
772 |
-
section: str,
|
773 |
-
option: str,
|
774 |
-
default: Union[int, float, str, bool, None] = None,
|
775 |
-
) -> Union[int, float, str, bool]:
|
776 |
-
"""Get an option's value.
|
777 |
-
|
778 |
-
If multiple values are specified for this option in the section, the last one
|
779 |
-
specified is returned.
|
780 |
-
|
781 |
-
:param default:
|
782 |
-
If not ``None``, the given default value will be returned in case the option
|
783 |
-
did not exist.
|
784 |
-
|
785 |
-
:return:
|
786 |
-
A properly typed value, either int, float or string
|
787 |
-
|
788 |
-
:raise TypeError:
|
789 |
-
In case the value could not be understood.
|
790 |
-
Otherwise the exceptions known to the ConfigParser will be raised.
|
791 |
-
"""
|
792 |
-
try:
|
793 |
-
valuestr = self.get(section, option)
|
794 |
-
except Exception:
|
795 |
-
if default is not None:
|
796 |
-
return default
|
797 |
-
raise
|
798 |
-
|
799 |
-
return self._string_to_value(valuestr)
|
800 |
-
|
801 |
-
def get_values(
|
802 |
-
self,
|
803 |
-
section: str,
|
804 |
-
option: str,
|
805 |
-
default: Union[int, float, str, bool, None] = None,
|
806 |
-
) -> List[Union[int, float, str, bool]]:
|
807 |
-
"""Get an option's values.
|
808 |
-
|
809 |
-
If multiple values are specified for this option in the section, all are
|
810 |
-
returned.
|
811 |
-
|
812 |
-
:param default:
|
813 |
-
If not ``None``, a list containing the given default value will be returned
|
814 |
-
in case the option did not exist.
|
815 |
-
|
816 |
-
:return:
|
817 |
-
A list of properly typed values, either int, float or string
|
818 |
-
|
819 |
-
:raise TypeError:
|
820 |
-
In case the value could not be understood.
|
821 |
-
Otherwise the exceptions known to the ConfigParser will be raised.
|
822 |
-
"""
|
823 |
-
try:
|
824 |
-
self.sections()
|
825 |
-
lst = self._sections[section].getall(option)
|
826 |
-
except Exception:
|
827 |
-
if default is not None:
|
828 |
-
return [default]
|
829 |
-
raise
|
830 |
-
|
831 |
-
return [self._string_to_value(valuestr) for valuestr in lst]
|
832 |
-
|
833 |
-
def _string_to_value(self, valuestr: str) -> Union[int, float, str, bool]:
|
834 |
-
types = (int, float)
|
835 |
-
for numtype in types:
|
836 |
-
try:
|
837 |
-
val = numtype(valuestr)
|
838 |
-
# truncated value ?
|
839 |
-
if val != float(valuestr):
|
840 |
-
continue
|
841 |
-
return val
|
842 |
-
except (ValueError, TypeError):
|
843 |
-
continue
|
844 |
-
# END for each numeric type
|
845 |
-
|
846 |
-
# Try boolean values as git uses them.
|
847 |
-
vl = valuestr.lower()
|
848 |
-
if vl == "false":
|
849 |
-
return False
|
850 |
-
if vl == "true":
|
851 |
-
return True
|
852 |
-
|
853 |
-
if not isinstance(valuestr, str):
|
854 |
-
raise TypeError(
|
855 |
-
"Invalid value type: only int, long, float and str are allowed",
|
856 |
-
valuestr,
|
857 |
-
)
|
858 |
-
|
859 |
-
return valuestr
|
860 |
-
|
861 |
-
def _value_to_string(self, value: Union[str, bytes, int, float, bool]) -> str:
|
862 |
-
if isinstance(value, (int, float, bool)):
|
863 |
-
return str(value)
|
864 |
-
return force_text(value)
|
865 |
-
|
866 |
-
@needs_values
|
867 |
-
@set_dirty_and_flush_changes
|
868 |
-
def set_value(self, section: str, option: str, value: Union[str, bytes, int, float, bool]) -> "GitConfigParser":
|
869 |
-
"""Set the given option in section to the given value.
|
870 |
-
|
871 |
-
This will create the section if required, and will not throw as opposed to the
|
872 |
-
default ConfigParser ``set`` method.
|
873 |
-
|
874 |
-
:param section:
|
875 |
-
Name of the section in which the option resides or should reside.
|
876 |
-
|
877 |
-
:param option:
|
878 |
-
Name of the options whose value to set.
|
879 |
-
|
880 |
-
:param value:
|
881 |
-
Value to set the option to. It must be a string or convertible to a string.
|
882 |
-
|
883 |
-
:return:
|
884 |
-
This instance
|
885 |
-
"""
|
886 |
-
if not self.has_section(section):
|
887 |
-
self.add_section(section)
|
888 |
-
self.set(section, option, self._value_to_string(value))
|
889 |
-
return self
|
890 |
-
|
891 |
-
@needs_values
|
892 |
-
@set_dirty_and_flush_changes
|
893 |
-
def add_value(self, section: str, option: str, value: Union[str, bytes, int, float, bool]) -> "GitConfigParser":
|
894 |
-
"""Add a value for the given option in section.
|
895 |
-
|
896 |
-
This will create the section if required, and will not throw as opposed to the
|
897 |
-
default ConfigParser ``set`` method. The value becomes the new value of the
|
898 |
-
option as returned by :meth:`get_value`, and appends to the list of values
|
899 |
-
returned by :meth:`get_values`.
|
900 |
-
|
901 |
-
:param section:
|
902 |
-
Name of the section in which the option resides or should reside.
|
903 |
-
|
904 |
-
:param option:
|
905 |
-
Name of the option.
|
906 |
-
|
907 |
-
:param value:
|
908 |
-
Value to add to option. It must be a string or convertible to a string.
|
909 |
-
|
910 |
-
:return:
|
911 |
-
This instance
|
912 |
-
"""
|
913 |
-
if not self.has_section(section):
|
914 |
-
self.add_section(section)
|
915 |
-
self._sections[section].add(option, self._value_to_string(value))
|
916 |
-
return self
|
917 |
-
|
918 |
-
def rename_section(self, section: str, new_name: str) -> "GitConfigParser":
|
919 |
-
"""Rename the given section to `new_name`.
|
920 |
-
|
921 |
-
:raise ValueError:
|
922 |
-
If:
|
923 |
-
|
924 |
-
* `section` doesn't exist.
|
925 |
-
* A section with `new_name` does already exist.
|
926 |
-
|
927 |
-
:return:
|
928 |
-
This instance
|
929 |
-
"""
|
930 |
-
if not self.has_section(section):
|
931 |
-
raise ValueError("Source section '%s' doesn't exist" % section)
|
932 |
-
if self.has_section(new_name):
|
933 |
-
raise ValueError("Destination section '%s' already exists" % new_name)
|
934 |
-
|
935 |
-
super().add_section(new_name)
|
936 |
-
new_section = self._sections[new_name]
|
937 |
-
for k, vs in self.items_all(section):
|
938 |
-
new_section.setall(k, vs)
|
939 |
-
# END for each value to copy
|
940 |
-
|
941 |
-
# This call writes back the changes, which is why we don't have the respective
|
942 |
-
# decorator.
|
943 |
-
self.remove_section(section)
|
944 |
-
return self
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/db.py
DELETED
@@ -1,71 +0,0 @@
|
|
1 |
-
# This module is part of GitPython and is released under the
|
2 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
3 |
-
|
4 |
-
"""Module with our own gitdb implementation - it uses the git command."""
|
5 |
-
|
6 |
-
__all__ = ["GitCmdObjectDB", "GitDB"]
|
7 |
-
|
8 |
-
from gitdb.base import OInfo, OStream
|
9 |
-
from gitdb.db import GitDB, LooseObjectDB
|
10 |
-
from gitdb.exc import BadObject
|
11 |
-
|
12 |
-
from git.util import bin_to_hex, hex_to_bin
|
13 |
-
from git.exc import GitCommandError
|
14 |
-
|
15 |
-
# typing-------------------------------------------------
|
16 |
-
|
17 |
-
from typing import TYPE_CHECKING
|
18 |
-
|
19 |
-
from git.types import PathLike
|
20 |
-
|
21 |
-
if TYPE_CHECKING:
|
22 |
-
from git.cmd import Git
|
23 |
-
|
24 |
-
# --------------------------------------------------------
|
25 |
-
|
26 |
-
|
27 |
-
class GitCmdObjectDB(LooseObjectDB):
|
28 |
-
"""A database representing the default git object store, which includes loose
|
29 |
-
objects, pack files and an alternates file.
|
30 |
-
|
31 |
-
It will create objects only in the loose object database.
|
32 |
-
"""
|
33 |
-
|
34 |
-
def __init__(self, root_path: PathLike, git: "Git") -> None:
|
35 |
-
"""Initialize this instance with the root and a git command."""
|
36 |
-
super().__init__(root_path)
|
37 |
-
self._git = git
|
38 |
-
|
39 |
-
def info(self, binsha: bytes) -> OInfo:
|
40 |
-
"""Get a git object header (using git itself)."""
|
41 |
-
hexsha, typename, size = self._git.get_object_header(bin_to_hex(binsha))
|
42 |
-
return OInfo(hex_to_bin(hexsha), typename, size)
|
43 |
-
|
44 |
-
def stream(self, binsha: bytes) -> OStream:
|
45 |
-
"""Get git object data as a stream supporting ``read()`` (using git itself)."""
|
46 |
-
hexsha, typename, size, stream = self._git.stream_object_data(bin_to_hex(binsha))
|
47 |
-
return OStream(hex_to_bin(hexsha), typename, size, stream)
|
48 |
-
|
49 |
-
# { Interface
|
50 |
-
|
51 |
-
def partial_to_complete_sha_hex(self, partial_hexsha: str) -> bytes:
|
52 |
-
"""
|
53 |
-
:return:
|
54 |
-
Full binary 20 byte sha from the given partial hexsha
|
55 |
-
|
56 |
-
:raise gitdb.exc.AmbiguousObjectName:
|
57 |
-
|
58 |
-
:raise gitdb.exc.BadObject:
|
59 |
-
|
60 |
-
:note:
|
61 |
-
Currently we only raise :exc:`~gitdb.exc.BadObject` as git does not
|
62 |
-
communicate ambiguous objects separately.
|
63 |
-
"""
|
64 |
-
try:
|
65 |
-
hexsha, _typename, _size = self._git.get_object_header(partial_hexsha)
|
66 |
-
return hex_to_bin(hexsha)
|
67 |
-
except (GitCommandError, ValueError) as e:
|
68 |
-
raise BadObject(partial_hexsha) from e
|
69 |
-
# END handle exceptions
|
70 |
-
|
71 |
-
# } END interface
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/diff.py
DELETED
@@ -1,775 +0,0 @@
|
|
1 |
-
# Copyright (C) 2008, 2009 Michael Trier ([email protected]) and contributors
|
2 |
-
#
|
3 |
-
# This module is part of GitPython and is released under the
|
4 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
5 |
-
|
6 |
-
__all__ = ["DiffConstants", "NULL_TREE", "INDEX", "Diffable", "DiffIndex", "Diff"]
|
7 |
-
|
8 |
-
import enum
|
9 |
-
import re
|
10 |
-
import warnings
|
11 |
-
|
12 |
-
from git.cmd import handle_process_output
|
13 |
-
from git.compat import defenc
|
14 |
-
from git.objects.blob import Blob
|
15 |
-
from git.objects.util import mode_str_to_int
|
16 |
-
from git.util import finalize_process, hex_to_bin
|
17 |
-
|
18 |
-
# typing ------------------------------------------------------------------
|
19 |
-
|
20 |
-
from typing import (
|
21 |
-
Any,
|
22 |
-
Iterator,
|
23 |
-
List,
|
24 |
-
Match,
|
25 |
-
Optional,
|
26 |
-
Tuple,
|
27 |
-
TYPE_CHECKING,
|
28 |
-
TypeVar,
|
29 |
-
Union,
|
30 |
-
cast,
|
31 |
-
)
|
32 |
-
from git.types import Literal, PathLike
|
33 |
-
|
34 |
-
if TYPE_CHECKING:
|
35 |
-
from subprocess import Popen
|
36 |
-
|
37 |
-
from git.cmd import Git
|
38 |
-
from git.objects.base import IndexObject
|
39 |
-
from git.objects.commit import Commit
|
40 |
-
from git.objects.tree import Tree
|
41 |
-
from git.repo.base import Repo
|
42 |
-
|
43 |
-
Lit_change_type = Literal["A", "D", "C", "M", "R", "T", "U"]
|
44 |
-
|
45 |
-
# ------------------------------------------------------------------------
|
46 |
-
|
47 |
-
|
48 |
-
@enum.unique
|
49 |
-
class DiffConstants(enum.Enum):
|
50 |
-
"""Special objects for :meth:`Diffable.diff`.
|
51 |
-
|
52 |
-
See the :meth:`Diffable.diff` method's ``other`` parameter, which accepts various
|
53 |
-
values including these.
|
54 |
-
|
55 |
-
:note:
|
56 |
-
These constants are also available as attributes of the :mod:`git.diff` module,
|
57 |
-
the :class:`Diffable` class and its subclasses and instances, and the top-level
|
58 |
-
:mod:`git` module.
|
59 |
-
"""
|
60 |
-
|
61 |
-
NULL_TREE = enum.auto()
|
62 |
-
"""Stand-in indicating you want to compare against the empty tree in diffs.
|
63 |
-
|
64 |
-
Also accessible as :const:`git.NULL_TREE`, :const:`git.diff.NULL_TREE`, and
|
65 |
-
:const:`Diffable.NULL_TREE`.
|
66 |
-
"""
|
67 |
-
|
68 |
-
INDEX = enum.auto()
|
69 |
-
"""Stand-in indicating you want to diff against the index.
|
70 |
-
|
71 |
-
Also accessible as :const:`git.INDEX`, :const:`git.diff.INDEX`, and
|
72 |
-
:const:`Diffable.INDEX`, as well as :const:`Diffable.Index`. The latter has been
|
73 |
-
kept for backward compatibility and made an alias of this, so it may still be used.
|
74 |
-
"""
|
75 |
-
|
76 |
-
|
77 |
-
NULL_TREE: Literal[DiffConstants.NULL_TREE] = DiffConstants.NULL_TREE
|
78 |
-
"""Stand-in indicating you want to compare against the empty tree in diffs.
|
79 |
-
|
80 |
-
See :meth:`Diffable.diff`, which accepts this as a value of its ``other`` parameter.
|
81 |
-
|
82 |
-
This is an alias of :const:`DiffConstants.NULL_TREE`, which may also be accessed as
|
83 |
-
:const:`git.NULL_TREE` and :const:`Diffable.NULL_TREE`.
|
84 |
-
"""
|
85 |
-
|
86 |
-
INDEX: Literal[DiffConstants.INDEX] = DiffConstants.INDEX
|
87 |
-
"""Stand-in indicating you want to diff against the index.
|
88 |
-
|
89 |
-
See :meth:`Diffable.diff`, which accepts this as a value of its ``other`` parameter.
|
90 |
-
|
91 |
-
This is an alias of :const:`DiffConstants.INDEX`, which may also be accessed as
|
92 |
-
:const:`git.INDEX` and :const:`Diffable.INDEX`, as well as :const:`Diffable.Index`.
|
93 |
-
"""
|
94 |
-
|
95 |
-
_octal_byte_re = re.compile(rb"\\([0-9]{3})")
|
96 |
-
|
97 |
-
|
98 |
-
def _octal_repl(matchobj: Match) -> bytes:
|
99 |
-
value = matchobj.group(1)
|
100 |
-
value = int(value, 8)
|
101 |
-
value = bytes(bytearray((value,)))
|
102 |
-
return value
|
103 |
-
|
104 |
-
|
105 |
-
def decode_path(path: bytes, has_ab_prefix: bool = True) -> Optional[bytes]:
|
106 |
-
if path == b"/dev/null":
|
107 |
-
return None
|
108 |
-
|
109 |
-
if path.startswith(b'"') and path.endswith(b'"'):
|
110 |
-
path = path[1:-1].replace(b"\\n", b"\n").replace(b"\\t", b"\t").replace(b'\\"', b'"').replace(b"\\\\", b"\\")
|
111 |
-
|
112 |
-
path = _octal_byte_re.sub(_octal_repl, path)
|
113 |
-
|
114 |
-
if has_ab_prefix:
|
115 |
-
assert path.startswith(b"a/") or path.startswith(b"b/")
|
116 |
-
path = path[2:]
|
117 |
-
|
118 |
-
return path
|
119 |
-
|
120 |
-
|
121 |
-
class Diffable:
|
122 |
-
"""Common interface for all objects that can be diffed against another object of
|
123 |
-
compatible type.
|
124 |
-
|
125 |
-
:note:
|
126 |
-
Subclasses require a :attr:`repo` member, as it is the case for
|
127 |
-
:class:`~git.objects.base.Object` instances. For practical reasons we do not
|
128 |
-
derive from :class:`~git.objects.base.Object`.
|
129 |
-
"""
|
130 |
-
|
131 |
-
__slots__ = ()
|
132 |
-
|
133 |
-
repo: "Repo"
|
134 |
-
"""Repository to operate on. Must be provided by subclass or sibling class."""
|
135 |
-
|
136 |
-
NULL_TREE = NULL_TREE
|
137 |
-
"""Stand-in indicating you want to compare against the empty tree in diffs.
|
138 |
-
|
139 |
-
See the :meth:`diff` method, which accepts this as a value of its ``other``
|
140 |
-
parameter.
|
141 |
-
|
142 |
-
This is the same as :const:`DiffConstants.NULL_TREE`, and may also be accessed as
|
143 |
-
:const:`git.NULL_TREE` and :const:`git.diff.NULL_TREE`.
|
144 |
-
"""
|
145 |
-
|
146 |
-
INDEX = INDEX
|
147 |
-
"""Stand-in indicating you want to diff against the index.
|
148 |
-
|
149 |
-
See the :meth:`diff` method, which accepts this as a value of its ``other``
|
150 |
-
parameter.
|
151 |
-
|
152 |
-
This is the same as :const:`DiffConstants.INDEX`, and may also be accessed as
|
153 |
-
:const:`git.INDEX` and :const:`git.diff.INDEX`, as well as :class:`Diffable.INDEX`,
|
154 |
-
which is kept for backward compatibility (it is now defined an alias of this).
|
155 |
-
"""
|
156 |
-
|
157 |
-
Index = INDEX
|
158 |
-
"""Stand-in indicating you want to diff against the index
|
159 |
-
(same as :const:`~Diffable.INDEX`).
|
160 |
-
|
161 |
-
This is an alias of :const:`~Diffable.INDEX`, for backward compatibility. See
|
162 |
-
:const:`~Diffable.INDEX` and :meth:`diff` for details.
|
163 |
-
|
164 |
-
:note:
|
165 |
-
Although always meant for use as an opaque constant, this was formerly defined
|
166 |
-
as a class. Its usage is unchanged, but static type annotations that attempt
|
167 |
-
to permit only this object must be changed to avoid new mypy errors. This was
|
168 |
-
previously not possible to do, though ``Type[Diffable.Index]`` approximated it.
|
169 |
-
It is now possible to do precisely, using ``Literal[DiffConstants.INDEX]``.
|
170 |
-
"""
|
171 |
-
|
172 |
-
def _process_diff_args(
|
173 |
-
self,
|
174 |
-
args: List[Union[PathLike, "Diffable"]],
|
175 |
-
) -> List[Union[PathLike, "Diffable"]]:
|
176 |
-
"""
|
177 |
-
:return:
|
178 |
-
Possibly altered version of the given args list.
|
179 |
-
This method is called right before git command execution.
|
180 |
-
Subclasses can use it to alter the behaviour of the superclass.
|
181 |
-
"""
|
182 |
-
return args
|
183 |
-
|
184 |
-
def diff(
|
185 |
-
self,
|
186 |
-
other: Union[DiffConstants, "Tree", "Commit", str, None] = INDEX,
|
187 |
-
paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
|
188 |
-
create_patch: bool = False,
|
189 |
-
**kwargs: Any,
|
190 |
-
) -> "DiffIndex":
|
191 |
-
"""Create diffs between two items being trees, trees and index or an index and
|
192 |
-
the working tree. Detects renames automatically.
|
193 |
-
|
194 |
-
:param other:
|
195 |
-
This the item to compare us with.
|
196 |
-
|
197 |
-
* If ``None``, we will be compared to the working tree.
|
198 |
-
|
199 |
-
* If a :class:`~git.types.Tree_ish` or string, it will be compared against
|
200 |
-
the respective tree.
|
201 |
-
|
202 |
-
* If :const:`INDEX`, it will be compared against the index.
|
203 |
-
|
204 |
-
* If :const:`NULL_TREE`, it will compare against the empty tree.
|
205 |
-
|
206 |
-
This parameter defaults to :const:`INDEX` (rather than ``None``) so that the
|
207 |
-
method will not by default fail on bare repositories.
|
208 |
-
|
209 |
-
:param paths:
|
210 |
-
This a list of paths or a single path to limit the diff to. It will only
|
211 |
-
include at least one of the given path or paths.
|
212 |
-
|
213 |
-
:param create_patch:
|
214 |
-
If ``True``, the returned :class:`Diff` contains a detailed patch that if
|
215 |
-
applied makes the self to other. Patches are somewhat costly as blobs have
|
216 |
-
to be read and diffed.
|
217 |
-
|
218 |
-
:param kwargs:
|
219 |
-
Additional arguments passed to :manpage:`git-diff(1)`, such as ``R=True`` to
|
220 |
-
swap both sides of the diff.
|
221 |
-
|
222 |
-
:return:
|
223 |
-
A :class:`DiffIndex` representing the computed diff.
|
224 |
-
|
225 |
-
:note:
|
226 |
-
On a bare repository, `other` needs to be provided as :const:`INDEX`, or as
|
227 |
-
an instance of :class:`~git.objects.tree.Tree` or
|
228 |
-
:class:`~git.objects.commit.Commit`, or a git command error will occur.
|
229 |
-
"""
|
230 |
-
args: List[Union[PathLike, Diffable]] = []
|
231 |
-
args.append("--abbrev=40") # We need full shas.
|
232 |
-
args.append("--full-index") # Get full index paths, not only filenames.
|
233 |
-
|
234 |
-
# Remove default '-M' arg (check for renames) if user is overriding it.
|
235 |
-
if not any(x in kwargs for x in ("find_renames", "no_renames", "M")):
|
236 |
-
args.append("-M")
|
237 |
-
|
238 |
-
if create_patch:
|
239 |
-
args.append("-p")
|
240 |
-
args.append("--no-ext-diff")
|
241 |
-
else:
|
242 |
-
args.append("--raw")
|
243 |
-
args.append("-z")
|
244 |
-
|
245 |
-
# Ensure we never see colored output.
|
246 |
-
# Fixes: https://github.com/gitpython-developers/GitPython/issues/172
|
247 |
-
args.append("--no-color")
|
248 |
-
|
249 |
-
if paths is not None and not isinstance(paths, (tuple, list)):
|
250 |
-
paths = [paths]
|
251 |
-
|
252 |
-
diff_cmd = self.repo.git.diff
|
253 |
-
if other is INDEX:
|
254 |
-
args.insert(0, "--cached")
|
255 |
-
elif other is NULL_TREE:
|
256 |
-
args.insert(0, "-r") # Recursive diff-tree.
|
257 |
-
args.insert(0, "--root")
|
258 |
-
diff_cmd = self.repo.git.diff_tree
|
259 |
-
elif other is not None:
|
260 |
-
args.insert(0, "-r") # Recursive diff-tree.
|
261 |
-
args.insert(0, other)
|
262 |
-
diff_cmd = self.repo.git.diff_tree
|
263 |
-
|
264 |
-
args.insert(0, self)
|
265 |
-
|
266 |
-
# paths is a list or tuple here, or None.
|
267 |
-
if paths:
|
268 |
-
args.append("--")
|
269 |
-
args.extend(paths)
|
270 |
-
# END paths handling
|
271 |
-
|
272 |
-
kwargs["as_process"] = True
|
273 |
-
proc = diff_cmd(*self._process_diff_args(args), **kwargs)
|
274 |
-
|
275 |
-
diff_method = Diff._index_from_patch_format if create_patch else Diff._index_from_raw_format
|
276 |
-
index = diff_method(self.repo, proc)
|
277 |
-
|
278 |
-
proc.wait()
|
279 |
-
return index
|
280 |
-
|
281 |
-
|
282 |
-
T_Diff = TypeVar("T_Diff", bound="Diff")
|
283 |
-
|
284 |
-
|
285 |
-
class DiffIndex(List[T_Diff]):
|
286 |
-
R"""An index for diffs, allowing a list of :class:`Diff`\s to be queried by the diff
|
287 |
-
properties.
|
288 |
-
|
289 |
-
The class improves the diff handling convenience.
|
290 |
-
"""
|
291 |
-
|
292 |
-
change_type = ("A", "C", "D", "R", "M", "T")
|
293 |
-
"""Change type invariant identifying possible ways a blob can have changed:
|
294 |
-
|
295 |
-
* ``A`` = Added
|
296 |
-
* ``D`` = Deleted
|
297 |
-
* ``R`` = Renamed
|
298 |
-
* ``M`` = Modified
|
299 |
-
* ``T`` = Changed in the type
|
300 |
-
"""
|
301 |
-
|
302 |
-
def iter_change_type(self, change_type: Lit_change_type) -> Iterator[T_Diff]:
|
303 |
-
"""
|
304 |
-
:return:
|
305 |
-
Iterator yielding :class:`Diff` instances that match the given `change_type`
|
306 |
-
|
307 |
-
:param change_type:
|
308 |
-
Member of :attr:`DiffIndex.change_type`, namely:
|
309 |
-
|
310 |
-
* 'A' for added paths
|
311 |
-
* 'D' for deleted paths
|
312 |
-
* 'R' for renamed paths
|
313 |
-
* 'M' for paths with modified data
|
314 |
-
* 'T' for changed in the type paths
|
315 |
-
"""
|
316 |
-
if change_type not in self.change_type:
|
317 |
-
raise ValueError("Invalid change type: %s" % change_type)
|
318 |
-
|
319 |
-
for diffidx in self:
|
320 |
-
if diffidx.change_type == change_type:
|
321 |
-
yield diffidx
|
322 |
-
elif change_type == "A" and diffidx.new_file:
|
323 |
-
yield diffidx
|
324 |
-
elif change_type == "D" and diffidx.deleted_file:
|
325 |
-
yield diffidx
|
326 |
-
elif change_type == "C" and diffidx.copied_file:
|
327 |
-
yield diffidx
|
328 |
-
elif change_type == "R" and diffidx.renamed:
|
329 |
-
yield diffidx
|
330 |
-
elif change_type == "M" and diffidx.a_blob and diffidx.b_blob and diffidx.a_blob != diffidx.b_blob:
|
331 |
-
yield diffidx
|
332 |
-
# END for each diff
|
333 |
-
|
334 |
-
|
335 |
-
class Diff:
|
336 |
-
"""A Diff contains diff information between two Trees.
|
337 |
-
|
338 |
-
It contains two sides a and b of the diff. Members are prefixed with "a" and "b"
|
339 |
-
respectively to indicate that.
|
340 |
-
|
341 |
-
Diffs keep information about the changed blob objects, the file mode, renames,
|
342 |
-
deletions and new files.
|
343 |
-
|
344 |
-
There are a few cases where ``None`` has to be expected as member variable value:
|
345 |
-
|
346 |
-
New File::
|
347 |
-
|
348 |
-
a_mode is None
|
349 |
-
a_blob is None
|
350 |
-
a_path is None
|
351 |
-
|
352 |
-
Deleted File::
|
353 |
-
|
354 |
-
b_mode is None
|
355 |
-
b_blob is None
|
356 |
-
b_path is None
|
357 |
-
|
358 |
-
Working Tree Blobs:
|
359 |
-
|
360 |
-
When comparing to working trees, the working tree blob will have a null hexsha
|
361 |
-
as a corresponding object does not yet exist. The mode will be null as well. The
|
362 |
-
path will be available, though.
|
363 |
-
|
364 |
-
If it is listed in a diff, the working tree version of the file must differ from
|
365 |
-
the version in the index or tree, and hence has been modified.
|
366 |
-
"""
|
367 |
-
|
368 |
-
# Precompiled regex.
|
369 |
-
re_header = re.compile(
|
370 |
-
rb"""
|
371 |
-
^diff[ ]--git
|
372 |
-
[ ](?P<a_path_fallback>"?[ab]/.+?"?)[ ](?P<b_path_fallback>"?[ab]/.+?"?)\n
|
373 |
-
(?:^old[ ]mode[ ](?P<old_mode>\d+)\n
|
374 |
-
^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
|
375 |
-
(?:^similarity[ ]index[ ]\d+%\n
|
376 |
-
^rename[ ]from[ ](?P<rename_from>.*)\n
|
377 |
-
^rename[ ]to[ ](?P<rename_to>.*)(?:\n|$))?
|
378 |
-
(?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
|
379 |
-
(?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
|
380 |
-
(?:^similarity[ ]index[ ]\d+%\n
|
381 |
-
^copy[ ]from[ ].*\n
|
382 |
-
^copy[ ]to[ ](?P<copied_file_name>.*)(?:\n|$))?
|
383 |
-
(?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
|
384 |
-
\.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
|
385 |
-
(?:^---[ ](?P<a_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
|
386 |
-
(?:^\+\+\+[ ](?P<b_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
|
387 |
-
""",
|
388 |
-
re.VERBOSE | re.MULTILINE,
|
389 |
-
)
|
390 |
-
|
391 |
-
# These can be used for comparisons.
|
392 |
-
NULL_HEX_SHA = "0" * 40
|
393 |
-
NULL_BIN_SHA = b"\0" * 20
|
394 |
-
|
395 |
-
__slots__ = (
|
396 |
-
"a_blob",
|
397 |
-
"b_blob",
|
398 |
-
"a_mode",
|
399 |
-
"b_mode",
|
400 |
-
"a_rawpath",
|
401 |
-
"b_rawpath",
|
402 |
-
"new_file",
|
403 |
-
"deleted_file",
|
404 |
-
"copied_file",
|
405 |
-
"raw_rename_from",
|
406 |
-
"raw_rename_to",
|
407 |
-
"diff",
|
408 |
-
"change_type",
|
409 |
-
"score",
|
410 |
-
)
|
411 |
-
|
412 |
-
def __init__(
|
413 |
-
self,
|
414 |
-
repo: "Repo",
|
415 |
-
a_rawpath: Optional[bytes],
|
416 |
-
b_rawpath: Optional[bytes],
|
417 |
-
a_blob_id: Union[str, bytes, None],
|
418 |
-
b_blob_id: Union[str, bytes, None],
|
419 |
-
a_mode: Union[bytes, str, None],
|
420 |
-
b_mode: Union[bytes, str, None],
|
421 |
-
new_file: bool,
|
422 |
-
deleted_file: bool,
|
423 |
-
copied_file: bool,
|
424 |
-
raw_rename_from: Optional[bytes],
|
425 |
-
raw_rename_to: Optional[bytes],
|
426 |
-
diff: Union[str, bytes, None],
|
427 |
-
change_type: Optional[Lit_change_type],
|
428 |
-
score: Optional[int],
|
429 |
-
) -> None:
|
430 |
-
assert a_rawpath is None or isinstance(a_rawpath, bytes)
|
431 |
-
assert b_rawpath is None or isinstance(b_rawpath, bytes)
|
432 |
-
self.a_rawpath = a_rawpath
|
433 |
-
self.b_rawpath = b_rawpath
|
434 |
-
|
435 |
-
self.a_mode = mode_str_to_int(a_mode) if a_mode else None
|
436 |
-
self.b_mode = mode_str_to_int(b_mode) if b_mode else None
|
437 |
-
|
438 |
-
# Determine whether this diff references a submodule. If it does then
|
439 |
-
# we need to overwrite "repo" to the corresponding submodule's repo instead.
|
440 |
-
if repo and a_rawpath:
|
441 |
-
for submodule in repo.submodules:
|
442 |
-
if submodule.path == a_rawpath.decode(defenc, "replace"):
|
443 |
-
if submodule.module_exists():
|
444 |
-
repo = submodule.module()
|
445 |
-
break
|
446 |
-
|
447 |
-
self.a_blob: Union["IndexObject", None]
|
448 |
-
if a_blob_id is None or a_blob_id == self.NULL_HEX_SHA:
|
449 |
-
self.a_blob = None
|
450 |
-
else:
|
451 |
-
self.a_blob = Blob(repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=self.a_path)
|
452 |
-
|
453 |
-
self.b_blob: Union["IndexObject", None]
|
454 |
-
if b_blob_id is None or b_blob_id == self.NULL_HEX_SHA:
|
455 |
-
self.b_blob = None
|
456 |
-
else:
|
457 |
-
self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=self.b_path)
|
458 |
-
|
459 |
-
self.new_file: bool = new_file
|
460 |
-
self.deleted_file: bool = deleted_file
|
461 |
-
self.copied_file: bool = copied_file
|
462 |
-
|
463 |
-
# Be clear and use None instead of empty strings.
|
464 |
-
assert raw_rename_from is None or isinstance(raw_rename_from, bytes)
|
465 |
-
assert raw_rename_to is None or isinstance(raw_rename_to, bytes)
|
466 |
-
self.raw_rename_from = raw_rename_from or None
|
467 |
-
self.raw_rename_to = raw_rename_to or None
|
468 |
-
|
469 |
-
self.diff = diff
|
470 |
-
self.change_type: Union[Lit_change_type, None] = change_type
|
471 |
-
self.score = score
|
472 |
-
|
473 |
-
def __eq__(self, other: object) -> bool:
|
474 |
-
for name in self.__slots__:
|
475 |
-
if getattr(self, name) != getattr(other, name):
|
476 |
-
return False
|
477 |
-
# END for each name
|
478 |
-
return True
|
479 |
-
|
480 |
-
def __ne__(self, other: object) -> bool:
|
481 |
-
return not (self == other)
|
482 |
-
|
483 |
-
def __hash__(self) -> int:
|
484 |
-
return hash(tuple(getattr(self, n) for n in self.__slots__))
|
485 |
-
|
486 |
-
def __str__(self) -> str:
|
487 |
-
h = "%s"
|
488 |
-
if self.a_blob:
|
489 |
-
h %= self.a_blob.path
|
490 |
-
elif self.b_blob:
|
491 |
-
h %= self.b_blob.path
|
492 |
-
|
493 |
-
msg = ""
|
494 |
-
line = None
|
495 |
-
line_length = 0
|
496 |
-
for b, n in zip((self.a_blob, self.b_blob), ("lhs", "rhs")):
|
497 |
-
if b:
|
498 |
-
line = "\n%s: %o | %s" % (n, b.mode, b.hexsha)
|
499 |
-
else:
|
500 |
-
line = "\n%s: None" % n
|
501 |
-
# END if blob is not None
|
502 |
-
line_length = max(len(line), line_length)
|
503 |
-
msg += line
|
504 |
-
# END for each blob
|
505 |
-
|
506 |
-
# Add headline.
|
507 |
-
h += "\n" + "=" * line_length
|
508 |
-
|
509 |
-
if self.deleted_file:
|
510 |
-
msg += "\nfile deleted in rhs"
|
511 |
-
if self.new_file:
|
512 |
-
msg += "\nfile added in rhs"
|
513 |
-
if self.copied_file:
|
514 |
-
msg += "\nfile %r copied from %r" % (self.b_path, self.a_path)
|
515 |
-
if self.rename_from:
|
516 |
-
msg += "\nfile renamed from %r" % self.rename_from
|
517 |
-
if self.rename_to:
|
518 |
-
msg += "\nfile renamed to %r" % self.rename_to
|
519 |
-
if self.diff:
|
520 |
-
msg += "\n---"
|
521 |
-
try:
|
522 |
-
msg += self.diff.decode(defenc) if isinstance(self.diff, bytes) else self.diff
|
523 |
-
except UnicodeDecodeError:
|
524 |
-
msg += "OMITTED BINARY DATA"
|
525 |
-
# END handle encoding
|
526 |
-
msg += "\n---"
|
527 |
-
# END diff info
|
528 |
-
|
529 |
-
return h + msg
|
530 |
-
|
531 |
-
@property
|
532 |
-
def a_path(self) -> Optional[str]:
|
533 |
-
return self.a_rawpath.decode(defenc, "replace") if self.a_rawpath else None
|
534 |
-
|
535 |
-
@property
|
536 |
-
def b_path(self) -> Optional[str]:
|
537 |
-
return self.b_rawpath.decode(defenc, "replace") if self.b_rawpath else None
|
538 |
-
|
539 |
-
@property
|
540 |
-
def rename_from(self) -> Optional[str]:
|
541 |
-
return self.raw_rename_from.decode(defenc, "replace") if self.raw_rename_from else None
|
542 |
-
|
543 |
-
@property
|
544 |
-
def rename_to(self) -> Optional[str]:
|
545 |
-
return self.raw_rename_to.decode(defenc, "replace") if self.raw_rename_to else None
|
546 |
-
|
547 |
-
@property
|
548 |
-
def renamed(self) -> bool:
|
549 |
-
"""Deprecated, use :attr:`renamed_file` instead.
|
550 |
-
|
551 |
-
:return:
|
552 |
-
``True`` if the blob of our diff has been renamed
|
553 |
-
|
554 |
-
:note:
|
555 |
-
This property is deprecated.
|
556 |
-
Please use the :attr:`renamed_file` property instead.
|
557 |
-
"""
|
558 |
-
warnings.warn(
|
559 |
-
"Diff.renamed is deprecated, use Diff.renamed_file instead",
|
560 |
-
DeprecationWarning,
|
561 |
-
stacklevel=2,
|
562 |
-
)
|
563 |
-
return self.renamed_file
|
564 |
-
|
565 |
-
@property
|
566 |
-
def renamed_file(self) -> bool:
|
567 |
-
""":return: ``True`` if the blob of our diff has been renamed"""
|
568 |
-
return self.rename_from != self.rename_to
|
569 |
-
|
570 |
-
@classmethod
|
571 |
-
def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_match: bytes) -> Optional[bytes]:
|
572 |
-
if path_match:
|
573 |
-
return decode_path(path_match)
|
574 |
-
|
575 |
-
if rename_match:
|
576 |
-
return decode_path(rename_match, has_ab_prefix=False)
|
577 |
-
|
578 |
-
if path_fallback_match:
|
579 |
-
return decode_path(path_fallback_match)
|
580 |
-
|
581 |
-
return None
|
582 |
-
|
583 |
-
@classmethod
|
584 |
-
def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoInterrupt"]) -> DiffIndex:
|
585 |
-
"""Create a new :class:`DiffIndex` from the given process output which must be
|
586 |
-
in patch format.
|
587 |
-
|
588 |
-
:param repo:
|
589 |
-
The repository we are operating on.
|
590 |
-
|
591 |
-
:param proc:
|
592 |
-
:manpage:`git-diff(1)` process to read from
|
593 |
-
(supports :class:`Git.AutoInterrupt <git.cmd.Git.AutoInterrupt>` wrapper).
|
594 |
-
|
595 |
-
:return:
|
596 |
-
:class:`DiffIndex`
|
597 |
-
"""
|
598 |
-
|
599 |
-
# FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise.
|
600 |
-
text_list: List[bytes] = []
|
601 |
-
handle_process_output(proc, text_list.append, None, finalize_process, decode_streams=False)
|
602 |
-
|
603 |
-
# For now, we have to bake the stream.
|
604 |
-
text = b"".join(text_list)
|
605 |
-
index: "DiffIndex" = DiffIndex()
|
606 |
-
previous_header: Union[Match[bytes], None] = None
|
607 |
-
header: Union[Match[bytes], None] = None
|
608 |
-
a_path, b_path = None, None # For mypy.
|
609 |
-
a_mode, b_mode = None, None # For mypy.
|
610 |
-
for _header in cls.re_header.finditer(text):
|
611 |
-
(
|
612 |
-
a_path_fallback,
|
613 |
-
b_path_fallback,
|
614 |
-
old_mode,
|
615 |
-
new_mode,
|
616 |
-
rename_from,
|
617 |
-
rename_to,
|
618 |
-
new_file_mode,
|
619 |
-
deleted_file_mode,
|
620 |
-
copied_file_name,
|
621 |
-
a_blob_id,
|
622 |
-
b_blob_id,
|
623 |
-
b_mode,
|
624 |
-
a_path,
|
625 |
-
b_path,
|
626 |
-
) = _header.groups()
|
627 |
-
|
628 |
-
new_file, deleted_file, copied_file = (
|
629 |
-
bool(new_file_mode),
|
630 |
-
bool(deleted_file_mode),
|
631 |
-
bool(copied_file_name),
|
632 |
-
)
|
633 |
-
|
634 |
-
a_path = cls._pick_best_path(a_path, rename_from, a_path_fallback)
|
635 |
-
b_path = cls._pick_best_path(b_path, rename_to, b_path_fallback)
|
636 |
-
|
637 |
-
# Our only means to find the actual text is to see what has not been matched
|
638 |
-
# by our regex, and then retro-actively assign it to our index.
|
639 |
-
if previous_header is not None:
|
640 |
-
index[-1].diff = text[previous_header.end() : _header.start()]
|
641 |
-
# END assign actual diff
|
642 |
-
|
643 |
-
# Make sure the mode is set if the path is set. Otherwise the resulting blob
|
644 |
-
# is invalid. We just use the one mode we should have parsed.
|
645 |
-
a_mode = old_mode or deleted_file_mode or (a_path and (b_mode or new_mode or new_file_mode))
|
646 |
-
b_mode = b_mode or new_mode or new_file_mode or (b_path and a_mode)
|
647 |
-
index.append(
|
648 |
-
Diff(
|
649 |
-
repo,
|
650 |
-
a_path,
|
651 |
-
b_path,
|
652 |
-
a_blob_id and a_blob_id.decode(defenc),
|
653 |
-
b_blob_id and b_blob_id.decode(defenc),
|
654 |
-
a_mode and a_mode.decode(defenc),
|
655 |
-
b_mode and b_mode.decode(defenc),
|
656 |
-
new_file,
|
657 |
-
deleted_file,
|
658 |
-
copied_file,
|
659 |
-
rename_from,
|
660 |
-
rename_to,
|
661 |
-
None,
|
662 |
-
None,
|
663 |
-
None,
|
664 |
-
)
|
665 |
-
)
|
666 |
-
|
667 |
-
previous_header = _header
|
668 |
-
header = _header
|
669 |
-
# END for each header we parse
|
670 |
-
if index and header:
|
671 |
-
index[-1].diff = text[header.end() :]
|
672 |
-
# END assign last diff
|
673 |
-
|
674 |
-
return index
|
675 |
-
|
676 |
-
@staticmethod
|
677 |
-
def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex) -> None:
|
678 |
-
lines = lines_bytes.decode(defenc)
|
679 |
-
|
680 |
-
# Discard everything before the first colon, and the colon itself.
|
681 |
-
_, _, lines = lines.partition(":")
|
682 |
-
|
683 |
-
for line in lines.split("\x00:"):
|
684 |
-
if not line:
|
685 |
-
# The line data is empty, skip.
|
686 |
-
continue
|
687 |
-
meta, _, path = line.partition("\x00")
|
688 |
-
path = path.rstrip("\x00")
|
689 |
-
a_blob_id: Optional[str]
|
690 |
-
b_blob_id: Optional[str]
|
691 |
-
old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4)
|
692 |
-
# Change type can be R100
|
693 |
-
# R: status letter
|
694 |
-
# 100: score (in case of copy and rename)
|
695 |
-
change_type: Lit_change_type = cast(Lit_change_type, _change_type[0])
|
696 |
-
score_str = "".join(_change_type[1:])
|
697 |
-
score = int(score_str) if score_str.isdigit() else None
|
698 |
-
path = path.strip()
|
699 |
-
a_path = path.encode(defenc)
|
700 |
-
b_path = path.encode(defenc)
|
701 |
-
deleted_file = False
|
702 |
-
new_file = False
|
703 |
-
copied_file = False
|
704 |
-
rename_from = None
|
705 |
-
rename_to = None
|
706 |
-
|
707 |
-
# NOTE: We cannot conclude from the existence of a blob to change type,
|
708 |
-
# as diffs with the working do not have blobs yet.
|
709 |
-
if change_type == "D":
|
710 |
-
b_blob_id = None # Optional[str]
|
711 |
-
deleted_file = True
|
712 |
-
elif change_type == "A":
|
713 |
-
a_blob_id = None
|
714 |
-
new_file = True
|
715 |
-
elif change_type == "C":
|
716 |
-
copied_file = True
|
717 |
-
a_path_str, b_path_str = path.split("\x00", 1)
|
718 |
-
a_path = a_path_str.encode(defenc)
|
719 |
-
b_path = b_path_str.encode(defenc)
|
720 |
-
elif change_type == "R":
|
721 |
-
a_path_str, b_path_str = path.split("\x00", 1)
|
722 |
-
a_path = a_path_str.encode(defenc)
|
723 |
-
b_path = b_path_str.encode(defenc)
|
724 |
-
rename_from, rename_to = a_path, b_path
|
725 |
-
elif change_type == "T":
|
726 |
-
# Nothing to do.
|
727 |
-
pass
|
728 |
-
# END add/remove handling
|
729 |
-
|
730 |
-
diff = Diff(
|
731 |
-
repo,
|
732 |
-
a_path,
|
733 |
-
b_path,
|
734 |
-
a_blob_id,
|
735 |
-
b_blob_id,
|
736 |
-
old_mode,
|
737 |
-
new_mode,
|
738 |
-
new_file,
|
739 |
-
deleted_file,
|
740 |
-
copied_file,
|
741 |
-
rename_from,
|
742 |
-
rename_to,
|
743 |
-
"",
|
744 |
-
change_type,
|
745 |
-
score,
|
746 |
-
)
|
747 |
-
index.append(diff)
|
748 |
-
|
749 |
-
@classmethod
|
750 |
-
def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex":
|
751 |
-
"""Create a new :class:`DiffIndex` from the given process output which must be
|
752 |
-
in raw format.
|
753 |
-
|
754 |
-
:param repo:
|
755 |
-
The repository we are operating on.
|
756 |
-
|
757 |
-
:param proc:
|
758 |
-
Process to read output from.
|
759 |
-
|
760 |
-
:return:
|
761 |
-
:class:`DiffIndex`
|
762 |
-
"""
|
763 |
-
# handles
|
764 |
-
# :100644 100644 687099101... 37c5e30c8... M .gitignore
|
765 |
-
|
766 |
-
index: "DiffIndex" = DiffIndex()
|
767 |
-
handle_process_output(
|
768 |
-
proc,
|
769 |
-
lambda byt: cls._handle_diff_line(byt, repo, index),
|
770 |
-
None,
|
771 |
-
finalize_process,
|
772 |
-
decode_streams=False,
|
773 |
-
)
|
774 |
-
|
775 |
-
return index
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/exc.py
DELETED
@@ -1,228 +0,0 @@
|
|
1 |
-
# Copyright (C) 2008, 2009 Michael Trier ([email protected]) and contributors
|
2 |
-
#
|
3 |
-
# This module is part of GitPython and is released under the
|
4 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
5 |
-
|
6 |
-
"""Exceptions thrown throughout the git package."""
|
7 |
-
|
8 |
-
__all__ = [
|
9 |
-
# Defined in gitdb.exc:
|
10 |
-
"AmbiguousObjectName",
|
11 |
-
"BadName",
|
12 |
-
"BadObject",
|
13 |
-
"BadObjectType",
|
14 |
-
"InvalidDBRoot",
|
15 |
-
"ODBError",
|
16 |
-
"ParseError",
|
17 |
-
"UnsupportedOperation",
|
18 |
-
# Introduced in this module:
|
19 |
-
"GitError",
|
20 |
-
"InvalidGitRepositoryError",
|
21 |
-
"WorkTreeRepositoryUnsupported",
|
22 |
-
"NoSuchPathError",
|
23 |
-
"UnsafeProtocolError",
|
24 |
-
"UnsafeOptionError",
|
25 |
-
"CommandError",
|
26 |
-
"GitCommandNotFound",
|
27 |
-
"GitCommandError",
|
28 |
-
"CheckoutError",
|
29 |
-
"CacheError",
|
30 |
-
"UnmergedEntriesError",
|
31 |
-
"HookExecutionError",
|
32 |
-
"RepositoryDirtyError",
|
33 |
-
]
|
34 |
-
|
35 |
-
from gitdb.exc import (
|
36 |
-
AmbiguousObjectName,
|
37 |
-
BadName,
|
38 |
-
BadObject,
|
39 |
-
BadObjectType,
|
40 |
-
InvalidDBRoot,
|
41 |
-
ODBError,
|
42 |
-
ParseError,
|
43 |
-
UnsupportedOperation,
|
44 |
-
)
|
45 |
-
|
46 |
-
from git.compat import safe_decode
|
47 |
-
from git.util import remove_password_if_present
|
48 |
-
|
49 |
-
# typing ----------------------------------------------------
|
50 |
-
|
51 |
-
from typing import List, Sequence, Tuple, TYPE_CHECKING, Union
|
52 |
-
|
53 |
-
from git.types import PathLike
|
54 |
-
|
55 |
-
if TYPE_CHECKING:
|
56 |
-
from git.repo.base import Repo
|
57 |
-
|
58 |
-
# ------------------------------------------------------------------
|
59 |
-
|
60 |
-
|
61 |
-
class GitError(Exception):
|
62 |
-
"""Base class for all package exceptions."""
|
63 |
-
|
64 |
-
|
65 |
-
class InvalidGitRepositoryError(GitError):
|
66 |
-
"""Thrown if the given repository appears to have an invalid format."""
|
67 |
-
|
68 |
-
|
69 |
-
class WorkTreeRepositoryUnsupported(InvalidGitRepositoryError):
|
70 |
-
"""Thrown to indicate we can't handle work tree repositories."""
|
71 |
-
|
72 |
-
|
73 |
-
class NoSuchPathError(GitError, OSError):
|
74 |
-
"""Thrown if a path could not be access by the system."""
|
75 |
-
|
76 |
-
|
77 |
-
class UnsafeProtocolError(GitError):
|
78 |
-
"""Thrown if unsafe protocols are passed without being explicitly allowed."""
|
79 |
-
|
80 |
-
|
81 |
-
class UnsafeOptionError(GitError):
|
82 |
-
"""Thrown if unsafe options are passed without being explicitly allowed."""
|
83 |
-
|
84 |
-
|
85 |
-
class CommandError(GitError):
|
86 |
-
"""Base class for exceptions thrown at every stage of :class:`~subprocess.Popen`
|
87 |
-
execution.
|
88 |
-
|
89 |
-
:param command:
|
90 |
-
A non-empty list of argv comprising the command-line.
|
91 |
-
"""
|
92 |
-
|
93 |
-
_msg = "Cmd('%s') failed%s"
|
94 |
-
"""Format string with 2 ``%s`` for ``<cmdline>`` and the rest.
|
95 |
-
|
96 |
-
For example: ``"'%s' failed%s"``
|
97 |
-
|
98 |
-
Subclasses may override this attribute, provided it is still in this form.
|
99 |
-
"""
|
100 |
-
|
101 |
-
def __init__(
|
102 |
-
self,
|
103 |
-
command: Union[List[str], Tuple[str, ...], str],
|
104 |
-
status: Union[str, int, None, Exception] = None,
|
105 |
-
stderr: Union[bytes, str, None] = None,
|
106 |
-
stdout: Union[bytes, str, None] = None,
|
107 |
-
) -> None:
|
108 |
-
if not isinstance(command, (tuple, list)):
|
109 |
-
command = command.split()
|
110 |
-
self.command = remove_password_if_present(command)
|
111 |
-
self.status = status
|
112 |
-
if status:
|
113 |
-
if isinstance(status, Exception):
|
114 |
-
status = "%s('%s')" % (type(status).__name__, safe_decode(str(status)))
|
115 |
-
else:
|
116 |
-
try:
|
117 |
-
status = "exit code(%s)" % int(status)
|
118 |
-
except (ValueError, TypeError):
|
119 |
-
s = safe_decode(str(status))
|
120 |
-
status = "'%s'" % s if isinstance(status, str) else s
|
121 |
-
|
122 |
-
self._cmd = safe_decode(self.command[0])
|
123 |
-
self._cmdline = " ".join(safe_decode(i) for i in self.command)
|
124 |
-
self._cause = status and " due to: %s" % status or "!"
|
125 |
-
stdout_decode = safe_decode(stdout)
|
126 |
-
stderr_decode = safe_decode(stderr)
|
127 |
-
self.stdout = stdout_decode and "\n stdout: '%s'" % stdout_decode or ""
|
128 |
-
self.stderr = stderr_decode and "\n stderr: '%s'" % stderr_decode or ""
|
129 |
-
|
130 |
-
def __str__(self) -> str:
|
131 |
-
return (self._msg + "\n cmdline: %s%s%s") % (
|
132 |
-
self._cmd,
|
133 |
-
self._cause,
|
134 |
-
self._cmdline,
|
135 |
-
self.stdout,
|
136 |
-
self.stderr,
|
137 |
-
)
|
138 |
-
|
139 |
-
|
140 |
-
class GitCommandNotFound(CommandError):
|
141 |
-
"""Thrown if we cannot find the ``git`` executable in the :envvar:`PATH` or at the
|
142 |
-
path given by the :envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable."""
|
143 |
-
|
144 |
-
def __init__(self, command: Union[List[str], Tuple[str], str], cause: Union[str, Exception]) -> None:
|
145 |
-
super().__init__(command, cause)
|
146 |
-
self._msg = "Cmd('%s') not found%s"
|
147 |
-
|
148 |
-
|
149 |
-
class GitCommandError(CommandError):
|
150 |
-
"""Thrown if execution of the git command fails with non-zero status code."""
|
151 |
-
|
152 |
-
def __init__(
|
153 |
-
self,
|
154 |
-
command: Union[List[str], Tuple[str, ...], str],
|
155 |
-
status: Union[str, int, None, Exception] = None,
|
156 |
-
stderr: Union[bytes, str, None] = None,
|
157 |
-
stdout: Union[bytes, str, None] = None,
|
158 |
-
) -> None:
|
159 |
-
super().__init__(command, status, stderr, stdout)
|
160 |
-
|
161 |
-
|
162 |
-
class CheckoutError(GitError):
|
163 |
-
"""Thrown if a file could not be checked out from the index as it contained
|
164 |
-
changes.
|
165 |
-
|
166 |
-
The :attr:`failed_files` attribute contains a list of relative paths that failed to
|
167 |
-
be checked out as they contained changes that did not exist in the index.
|
168 |
-
|
169 |
-
The :attr:`failed_reasons` attribute contains a string informing about the actual
|
170 |
-
cause of the issue.
|
171 |
-
|
172 |
-
The :attr:`valid_files` attribute contains a list of relative paths to files that
|
173 |
-
were checked out successfully and hence match the version stored in the index.
|
174 |
-
"""
|
175 |
-
|
176 |
-
def __init__(
|
177 |
-
self,
|
178 |
-
message: str,
|
179 |
-
failed_files: Sequence[PathLike],
|
180 |
-
valid_files: Sequence[PathLike],
|
181 |
-
failed_reasons: List[str],
|
182 |
-
) -> None:
|
183 |
-
Exception.__init__(self, message)
|
184 |
-
self.failed_files = failed_files
|
185 |
-
self.failed_reasons = failed_reasons
|
186 |
-
self.valid_files = valid_files
|
187 |
-
|
188 |
-
def __str__(self) -> str:
|
189 |
-
return Exception.__str__(self) + ":%s" % self.failed_files
|
190 |
-
|
191 |
-
|
192 |
-
class CacheError(GitError):
|
193 |
-
"""Base for all errors related to the git index, which is called "cache"
|
194 |
-
internally."""
|
195 |
-
|
196 |
-
|
197 |
-
class UnmergedEntriesError(CacheError):
|
198 |
-
"""Thrown if an operation cannot proceed as there are still unmerged
|
199 |
-
entries in the cache."""
|
200 |
-
|
201 |
-
|
202 |
-
class HookExecutionError(CommandError):
|
203 |
-
"""Thrown if a hook exits with a non-zero exit code.
|
204 |
-
|
205 |
-
This provides access to the exit code and the string returned via standard output.
|
206 |
-
"""
|
207 |
-
|
208 |
-
def __init__(
|
209 |
-
self,
|
210 |
-
command: Union[List[str], Tuple[str, ...], str],
|
211 |
-
status: Union[str, int, None, Exception],
|
212 |
-
stderr: Union[bytes, str, None] = None,
|
213 |
-
stdout: Union[bytes, str, None] = None,
|
214 |
-
) -> None:
|
215 |
-
super().__init__(command, status, stderr, stdout)
|
216 |
-
self._msg = "Hook('%s') failed%s"
|
217 |
-
|
218 |
-
|
219 |
-
class RepositoryDirtyError(GitError):
|
220 |
-
"""Thrown whenever an operation on a repository fails as it has uncommitted changes
|
221 |
-
that would be overwritten."""
|
222 |
-
|
223 |
-
def __init__(self, repo: "Repo", message: str) -> None:
|
224 |
-
self.repo = repo
|
225 |
-
self.message = message
|
226 |
-
|
227 |
-
def __str__(self) -> str:
|
228 |
-
return "Operation cannot be performed on %r: %s" % (self.repo, self.message)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/index/__init__.py
DELETED
@@ -1,16 +0,0 @@
|
|
1 |
-
# This module is part of GitPython and is released under the
|
2 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
3 |
-
|
4 |
-
"""Initialize the index package."""
|
5 |
-
|
6 |
-
__all__ = [
|
7 |
-
"BaseIndexEntry",
|
8 |
-
"BlobFilter",
|
9 |
-
"CheckoutError",
|
10 |
-
"IndexEntry",
|
11 |
-
"IndexFile",
|
12 |
-
"StageType",
|
13 |
-
]
|
14 |
-
|
15 |
-
from .base import CheckoutError, IndexFile
|
16 |
-
from .typ import BaseIndexEntry, BlobFilter, IndexEntry, StageType
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/index/__pycache__/__init__.cpython-311.pyc
DELETED
Binary file (573 Bytes)
|
|
ILYA/Lib/site-packages/git/index/__pycache__/base.cpython-311.pyc
DELETED
Binary file (69.5 kB)
|
|
ILYA/Lib/site-packages/git/index/__pycache__/fun.cpython-311.pyc
DELETED
Binary file (18.9 kB)
|
|
ILYA/Lib/site-packages/git/index/__pycache__/typ.cpython-311.pyc
DELETED
Binary file (10.3 kB)
|
|
ILYA/Lib/site-packages/git/index/__pycache__/util.cpython-311.pyc
DELETED
Binary file (6.24 kB)
|
|
ILYA/Lib/site-packages/git/index/base.py
DELETED
@@ -1,1518 +0,0 @@
|
|
1 |
-
# Copyright (C) 2008, 2009 Michael Trier ([email protected]) and contributors
|
2 |
-
#
|
3 |
-
# This module is part of GitPython and is released under the
|
4 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
5 |
-
|
6 |
-
"""Module containing :class:`IndexFile`, an Index implementation facilitating all kinds
|
7 |
-
of index manipulations such as querying and merging."""
|
8 |
-
|
9 |
-
__all__ = ["IndexFile", "CheckoutError", "StageType"]
|
10 |
-
|
11 |
-
import contextlib
|
12 |
-
import datetime
|
13 |
-
import glob
|
14 |
-
from io import BytesIO
|
15 |
-
import os
|
16 |
-
import os.path as osp
|
17 |
-
from stat import S_ISLNK
|
18 |
-
import subprocess
|
19 |
-
import sys
|
20 |
-
import tempfile
|
21 |
-
|
22 |
-
from gitdb.base import IStream
|
23 |
-
from gitdb.db import MemoryDB
|
24 |
-
|
25 |
-
from git.compat import defenc, force_bytes
|
26 |
-
import git.diff as git_diff
|
27 |
-
from git.exc import CheckoutError, GitCommandError, GitError, InvalidGitRepositoryError
|
28 |
-
from git.objects import Blob, Commit, Object, Submodule, Tree
|
29 |
-
from git.objects.util import Serializable
|
30 |
-
from git.util import (
|
31 |
-
LazyMixin,
|
32 |
-
LockedFD,
|
33 |
-
join_path_native,
|
34 |
-
file_contents_ro,
|
35 |
-
to_native_path_linux,
|
36 |
-
unbare_repo,
|
37 |
-
to_bin_sha,
|
38 |
-
)
|
39 |
-
|
40 |
-
from .fun import (
|
41 |
-
S_IFGITLINK,
|
42 |
-
aggressive_tree_merge,
|
43 |
-
entry_key,
|
44 |
-
read_cache,
|
45 |
-
run_commit_hook,
|
46 |
-
stat_mode_to_index_mode,
|
47 |
-
write_cache,
|
48 |
-
write_tree_from_cache,
|
49 |
-
)
|
50 |
-
from .typ import BaseIndexEntry, IndexEntry, StageType
|
51 |
-
from .util import TemporaryFileSwap, post_clear_cache, default_index, git_working_dir
|
52 |
-
|
53 |
-
# typing -----------------------------------------------------------------------------
|
54 |
-
|
55 |
-
from typing import (
|
56 |
-
Any,
|
57 |
-
BinaryIO,
|
58 |
-
Callable,
|
59 |
-
Dict,
|
60 |
-
Generator,
|
61 |
-
IO,
|
62 |
-
Iterable,
|
63 |
-
Iterator,
|
64 |
-
List,
|
65 |
-
NoReturn,
|
66 |
-
Sequence,
|
67 |
-
TYPE_CHECKING,
|
68 |
-
Tuple,
|
69 |
-
Union,
|
70 |
-
)
|
71 |
-
|
72 |
-
from git.types import Literal, PathLike
|
73 |
-
|
74 |
-
if TYPE_CHECKING:
|
75 |
-
from subprocess import Popen
|
76 |
-
|
77 |
-
from git.refs.reference import Reference
|
78 |
-
from git.repo import Repo
|
79 |
-
from git.util import Actor
|
80 |
-
|
81 |
-
|
82 |
-
Treeish = Union[Tree, Commit, str, bytes]
|
83 |
-
|
84 |
-
# ------------------------------------------------------------------------------------
|
85 |
-
|
86 |
-
|
87 |
-
@contextlib.contextmanager
|
88 |
-
def _named_temporary_file_for_subprocess(directory: PathLike) -> Generator[str, None, None]:
|
89 |
-
"""Create a named temporary file git subprocesses can open, deleting it afterward.
|
90 |
-
|
91 |
-
:param directory:
|
92 |
-
The directory in which the file is created.
|
93 |
-
|
94 |
-
:return:
|
95 |
-
A context manager object that creates the file and provides its name on entry,
|
96 |
-
and deletes it on exit.
|
97 |
-
"""
|
98 |
-
if sys.platform == "win32":
|
99 |
-
fd, name = tempfile.mkstemp(dir=directory)
|
100 |
-
os.close(fd)
|
101 |
-
try:
|
102 |
-
yield name
|
103 |
-
finally:
|
104 |
-
os.remove(name)
|
105 |
-
else:
|
106 |
-
with tempfile.NamedTemporaryFile(dir=directory) as ctx:
|
107 |
-
yield ctx.name
|
108 |
-
|
109 |
-
|
110 |
-
class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
|
111 |
-
"""An Index that can be manipulated using a native implementation in order to save
|
112 |
-
git command function calls wherever possible.
|
113 |
-
|
114 |
-
This provides custom merging facilities allowing to merge without actually changing
|
115 |
-
your index or your working tree. This way you can perform your own test merges based
|
116 |
-
on the index only without having to deal with the working copy. This is useful in
|
117 |
-
case of partial working trees.
|
118 |
-
|
119 |
-
Entries:
|
120 |
-
|
121 |
-
The index contains an entries dict whose keys are tuples of type
|
122 |
-
:class:`~git.index.typ.IndexEntry` to facilitate access.
|
123 |
-
|
124 |
-
You may read the entries dict or manipulate it using IndexEntry instance, i.e.::
|
125 |
-
|
126 |
-
index.entries[index.entry_key(index_entry_instance)] = index_entry_instance
|
127 |
-
|
128 |
-
Make sure you use :meth:`index.write() <write>` once you are done manipulating the
|
129 |
-
index directly before operating on it using the git command.
|
130 |
-
"""
|
131 |
-
|
132 |
-
__slots__ = ("repo", "version", "entries", "_extension_data", "_file_path")
|
133 |
-
|
134 |
-
_VERSION = 2
|
135 |
-
"""The latest version we support."""
|
136 |
-
|
137 |
-
S_IFGITLINK = S_IFGITLINK
|
138 |
-
"""Flags for a submodule."""
|
139 |
-
|
140 |
-
def __init__(self, repo: "Repo", file_path: Union[PathLike, None] = None) -> None:
|
141 |
-
"""Initialize this Index instance, optionally from the given `file_path`.
|
142 |
-
|
143 |
-
If no `file_path` is given, we will be created from the current index file.
|
144 |
-
|
145 |
-
If a stream is not given, the stream will be initialized from the current
|
146 |
-
repository's index on demand.
|
147 |
-
"""
|
148 |
-
self.repo = repo
|
149 |
-
self.version = self._VERSION
|
150 |
-
self._extension_data = b""
|
151 |
-
self._file_path: PathLike = file_path or self._index_path()
|
152 |
-
|
153 |
-
def _set_cache_(self, attr: str) -> None:
|
154 |
-
if attr == "entries":
|
155 |
-
try:
|
156 |
-
fd = os.open(self._file_path, os.O_RDONLY)
|
157 |
-
except OSError:
|
158 |
-
# In new repositories, there may be no index, which means we are empty.
|
159 |
-
self.entries: Dict[Tuple[PathLike, StageType], IndexEntry] = {}
|
160 |
-
return
|
161 |
-
# END exception handling
|
162 |
-
|
163 |
-
try:
|
164 |
-
stream = file_contents_ro(fd, stream=True, allow_mmap=True)
|
165 |
-
finally:
|
166 |
-
os.close(fd)
|
167 |
-
|
168 |
-
self._deserialize(stream)
|
169 |
-
else:
|
170 |
-
super()._set_cache_(attr)
|
171 |
-
|
172 |
-
def _index_path(self) -> PathLike:
|
173 |
-
if self.repo.git_dir:
|
174 |
-
return join_path_native(self.repo.git_dir, "index")
|
175 |
-
else:
|
176 |
-
raise GitCommandError("No git directory given to join index path")
|
177 |
-
|
178 |
-
@property
|
179 |
-
def path(self) -> PathLike:
|
180 |
-
""":return: Path to the index file we are representing"""
|
181 |
-
return self._file_path
|
182 |
-
|
183 |
-
def _delete_entries_cache(self) -> None:
|
184 |
-
"""Safely clear the entries cache so it can be recreated."""
|
185 |
-
try:
|
186 |
-
del self.entries
|
187 |
-
except AttributeError:
|
188 |
-
# It failed in Python 2.6.5 with AttributeError.
|
189 |
-
# FIXME: Look into whether we can just remove this except clause now.
|
190 |
-
pass
|
191 |
-
# END exception handling
|
192 |
-
|
193 |
-
# { Serializable Interface
|
194 |
-
|
195 |
-
def _deserialize(self, stream: IO) -> "IndexFile":
|
196 |
-
"""Initialize this instance with index values read from the given stream."""
|
197 |
-
self.version, self.entries, self._extension_data, _conten_sha = read_cache(stream)
|
198 |
-
return self
|
199 |
-
|
200 |
-
def _entries_sorted(self) -> List[IndexEntry]:
|
201 |
-
""":return: List of entries, in a sorted fashion, first by path, then by stage"""
|
202 |
-
return sorted(self.entries.values(), key=lambda e: (e.path, e.stage))
|
203 |
-
|
204 |
-
def _serialize(self, stream: IO, ignore_extension_data: bool = False) -> "IndexFile":
|
205 |
-
entries = self._entries_sorted()
|
206 |
-
extension_data = self._extension_data # type: Union[None, bytes]
|
207 |
-
if ignore_extension_data:
|
208 |
-
extension_data = None
|
209 |
-
write_cache(entries, stream, extension_data)
|
210 |
-
return self
|
211 |
-
|
212 |
-
# } END serializable interface
|
213 |
-
|
214 |
-
def write(
|
215 |
-
self,
|
216 |
-
file_path: Union[None, PathLike] = None,
|
217 |
-
ignore_extension_data: bool = False,
|
218 |
-
) -> None:
|
219 |
-
"""Write the current state to our file path or to the given one.
|
220 |
-
|
221 |
-
:param file_path:
|
222 |
-
If ``None``, we will write to our stored file path from which we have been
|
223 |
-
initialized. Otherwise we write to the given file path. Please note that
|
224 |
-
this will change the `file_path` of this index to the one you gave.
|
225 |
-
|
226 |
-
:param ignore_extension_data:
|
227 |
-
If ``True``, the TREE type extension data read in the index will not be
|
228 |
-
written to disk. NOTE that no extension data is actually written. Use this
|
229 |
-
if you have altered the index and would like to use
|
230 |
-
:manpage:`git-write-tree(1)` afterwards to create a tree representing your
|
231 |
-
written changes. If this data is present in the written index,
|
232 |
-
:manpage:`git-write-tree(1)` will instead write the stored/cached tree.
|
233 |
-
Alternatively, use :meth:`write_tree` to handle this case automatically.
|
234 |
-
"""
|
235 |
-
# Make sure we have our entries read before getting a write lock.
|
236 |
-
# Otherwise it would be done when streaming.
|
237 |
-
# This can happen if one doesn't change the index, but writes it right away.
|
238 |
-
self.entries # noqa: B018
|
239 |
-
lfd = LockedFD(file_path or self._file_path)
|
240 |
-
stream = lfd.open(write=True, stream=True)
|
241 |
-
|
242 |
-
try:
|
243 |
-
self._serialize(stream, ignore_extension_data)
|
244 |
-
except BaseException:
|
245 |
-
lfd.rollback()
|
246 |
-
raise
|
247 |
-
|
248 |
-
lfd.commit()
|
249 |
-
|
250 |
-
# Make sure we represent what we have written.
|
251 |
-
if file_path is not None:
|
252 |
-
self._file_path = file_path
|
253 |
-
|
254 |
-
@post_clear_cache
|
255 |
-
@default_index
|
256 |
-
def merge_tree(self, rhs: Treeish, base: Union[None, Treeish] = None) -> "IndexFile":
|
257 |
-
"""Merge the given `rhs` treeish into the current index, possibly taking
|
258 |
-
a common base treeish into account.
|
259 |
-
|
260 |
-
As opposed to the :func:`from_tree` method, this allows you to use an already
|
261 |
-
existing tree as the left side of the merge.
|
262 |
-
|
263 |
-
:param rhs:
|
264 |
-
Treeish reference pointing to the 'other' side of the merge.
|
265 |
-
|
266 |
-
:param base:
|
267 |
-
Optional treeish reference pointing to the common base of `rhs` and this
|
268 |
-
index which equals lhs.
|
269 |
-
|
270 |
-
:return:
|
271 |
-
self (containing the merge and possibly unmerged entries in case of
|
272 |
-
conflicts)
|
273 |
-
|
274 |
-
:raise git.exc.GitCommandError:
|
275 |
-
If there is a merge conflict. The error will be raised at the first
|
276 |
-
conflicting path. If you want to have proper merge resolution to be done by
|
277 |
-
yourself, you have to commit the changed index (or make a valid tree from
|
278 |
-
it) and retry with a three-way :meth:`index.from_tree <from_tree>` call.
|
279 |
-
"""
|
280 |
-
# -i : ignore working tree status
|
281 |
-
# --aggressive : handle more merge cases
|
282 |
-
# -m : do an actual merge
|
283 |
-
args: List[Union[Treeish, str]] = ["--aggressive", "-i", "-m"]
|
284 |
-
if base is not None:
|
285 |
-
args.append(base)
|
286 |
-
args.append(rhs)
|
287 |
-
|
288 |
-
self.repo.git.read_tree(args)
|
289 |
-
return self
|
290 |
-
|
291 |
-
@classmethod
|
292 |
-
def new(cls, repo: "Repo", *tree_sha: Union[str, Tree]) -> "IndexFile":
|
293 |
-
"""Merge the given treeish revisions into a new index which is returned.
|
294 |
-
|
295 |
-
This method behaves like ``git-read-tree --aggressive`` when doing the merge.
|
296 |
-
|
297 |
-
:param repo:
|
298 |
-
The repository treeish are located in.
|
299 |
-
|
300 |
-
:param tree_sha:
|
301 |
-
20 byte or 40 byte tree sha or tree objects.
|
302 |
-
|
303 |
-
:return:
|
304 |
-
New :class:`IndexFile` instance. Its path will be undefined.
|
305 |
-
If you intend to write such a merged Index, supply an alternate
|
306 |
-
``file_path`` to its :meth:`write` method.
|
307 |
-
"""
|
308 |
-
tree_sha_bytes: List[bytes] = [to_bin_sha(str(t)) for t in tree_sha]
|
309 |
-
base_entries = aggressive_tree_merge(repo.odb, tree_sha_bytes)
|
310 |
-
|
311 |
-
inst = cls(repo)
|
312 |
-
# Convert to entries dict.
|
313 |
-
entries: Dict[Tuple[PathLike, int], IndexEntry] = dict(
|
314 |
-
zip(
|
315 |
-
((e.path, e.stage) for e in base_entries),
|
316 |
-
(IndexEntry.from_base(e) for e in base_entries),
|
317 |
-
)
|
318 |
-
)
|
319 |
-
|
320 |
-
inst.entries = entries
|
321 |
-
return inst
|
322 |
-
|
323 |
-
@classmethod
|
324 |
-
def from_tree(cls, repo: "Repo", *treeish: Treeish, **kwargs: Any) -> "IndexFile":
|
325 |
-
R"""Merge the given treeish revisions into a new index which is returned.
|
326 |
-
The original index will remain unaltered.
|
327 |
-
|
328 |
-
:param repo:
|
329 |
-
The repository treeish are located in.
|
330 |
-
|
331 |
-
:param treeish:
|
332 |
-
One, two or three :class:`~git.objects.tree.Tree` objects,
|
333 |
-
:class:`~git.objects.commit.Commit`\s or 40 byte hexshas.
|
334 |
-
|
335 |
-
The result changes according to the amount of trees:
|
336 |
-
|
337 |
-
1. If 1 Tree is given, it will just be read into a new index.
|
338 |
-
2. If 2 Trees are given, they will be merged into a new index using a two
|
339 |
-
way merge algorithm. Tree 1 is the 'current' tree, tree 2 is the 'other'
|
340 |
-
one. It behaves like a fast-forward.
|
341 |
-
3. If 3 Trees are given, a 3-way merge will be performed with the first tree
|
342 |
-
being the common ancestor of tree 2 and tree 3. Tree 2 is the 'current'
|
343 |
-
tree, tree 3 is the 'other' one.
|
344 |
-
|
345 |
-
:param kwargs:
|
346 |
-
Additional arguments passed to :manpage:`git-read-tree(1)`.
|
347 |
-
|
348 |
-
:return:
|
349 |
-
New :class:`IndexFile` instance. It will point to a temporary index location
|
350 |
-
which does not exist anymore. If you intend to write such a merged Index,
|
351 |
-
supply an alternate ``file_path`` to its :meth:`write` method.
|
352 |
-
|
353 |
-
:note:
|
354 |
-
In the three-way merge case, ``--aggressive`` will be specified to
|
355 |
-
automatically resolve more cases in a commonly correct manner. Specify
|
356 |
-
``trivial=True`` as a keyword argument to override that.
|
357 |
-
|
358 |
-
As the underlying :manpage:`git-read-tree(1)` command takes into account the
|
359 |
-
current index, it will be temporarily moved out of the way to prevent any
|
360 |
-
unexpected interference.
|
361 |
-
"""
|
362 |
-
if len(treeish) == 0 or len(treeish) > 3:
|
363 |
-
raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish))
|
364 |
-
|
365 |
-
arg_list: List[Union[Treeish, str]] = []
|
366 |
-
# Ignore that the working tree and index possibly are out of date.
|
367 |
-
if len(treeish) > 1:
|
368 |
-
# Drop unmerged entries when reading our index and merging.
|
369 |
-
arg_list.append("--reset")
|
370 |
-
# Handle non-trivial cases the way a real merge does.
|
371 |
-
arg_list.append("--aggressive")
|
372 |
-
# END merge handling
|
373 |
-
|
374 |
-
# Create the temporary file in the .git directory to be sure renaming
|
375 |
-
# works - /tmp/ directories could be on another device.
|
376 |
-
with _named_temporary_file_for_subprocess(repo.git_dir) as tmp_index:
|
377 |
-
arg_list.append("--index-output=%s" % tmp_index)
|
378 |
-
arg_list.extend(treeish)
|
379 |
-
|
380 |
-
# Move the current index out of the way - otherwise the merge may fail as it
|
381 |
-
# considers existing entries. Moving it essentially clears the index.
|
382 |
-
# Unfortunately there is no 'soft' way to do it.
|
383 |
-
# The TemporaryFileSwap ensures the original file gets put back.
|
384 |
-
with TemporaryFileSwap(join_path_native(repo.git_dir, "index")):
|
385 |
-
repo.git.read_tree(*arg_list, **kwargs)
|
386 |
-
index = cls(repo, tmp_index)
|
387 |
-
index.entries # noqa: B018 # Force it to read the file as we will delete the temp-file.
|
388 |
-
return index
|
389 |
-
# END index merge handling
|
390 |
-
|
391 |
-
# UTILITIES
|
392 |
-
|
393 |
-
@unbare_repo
|
394 |
-
def _iter_expand_paths(self: "IndexFile", paths: Sequence[PathLike]) -> Iterator[PathLike]:
|
395 |
-
"""Expand the directories in list of paths to the corresponding paths
|
396 |
-
accordingly.
|
397 |
-
|
398 |
-
:note:
|
399 |
-
git will add items multiple times even if a glob overlapped with manually
|
400 |
-
specified paths or if paths where specified multiple times - we respect that
|
401 |
-
and do not prune.
|
402 |
-
"""
|
403 |
-
|
404 |
-
def raise_exc(e: Exception) -> NoReturn:
|
405 |
-
raise e
|
406 |
-
|
407 |
-
r = str(self.repo.working_tree_dir)
|
408 |
-
rs = r + os.sep
|
409 |
-
for path in paths:
|
410 |
-
abs_path = str(path)
|
411 |
-
if not osp.isabs(abs_path):
|
412 |
-
abs_path = osp.join(r, path)
|
413 |
-
# END make absolute path
|
414 |
-
|
415 |
-
try:
|
416 |
-
st = os.lstat(abs_path) # Handles non-symlinks as well.
|
417 |
-
except OSError:
|
418 |
-
# The lstat call may fail as the path may contain globs as well.
|
419 |
-
pass
|
420 |
-
else:
|
421 |
-
if S_ISLNK(st.st_mode):
|
422 |
-
yield abs_path.replace(rs, "")
|
423 |
-
continue
|
424 |
-
# END check symlink
|
425 |
-
|
426 |
-
# If the path is not already pointing to an existing file, resolve globs if possible.
|
427 |
-
if not os.path.exists(abs_path) and ("?" in abs_path or "*" in abs_path or "[" in abs_path):
|
428 |
-
resolved_paths = glob.glob(abs_path)
|
429 |
-
# not abs_path in resolved_paths:
|
430 |
-
# A glob() resolving to the same path we are feeding it with is a
|
431 |
-
# glob() that failed to resolve. If we continued calling ourselves
|
432 |
-
# we'd endlessly recurse. If the condition below evaluates to true
|
433 |
-
# then we are likely dealing with a file whose name contains wildcard
|
434 |
-
# characters.
|
435 |
-
if abs_path not in resolved_paths:
|
436 |
-
for f in self._iter_expand_paths(glob.glob(abs_path)):
|
437 |
-
yield str(f).replace(rs, "")
|
438 |
-
continue
|
439 |
-
# END glob handling
|
440 |
-
try:
|
441 |
-
for root, _dirs, files in os.walk(abs_path, onerror=raise_exc):
|
442 |
-
for rela_file in files:
|
443 |
-
# Add relative paths only.
|
444 |
-
yield osp.join(root.replace(rs, ""), rela_file)
|
445 |
-
# END for each file in subdir
|
446 |
-
# END for each subdirectory
|
447 |
-
except OSError:
|
448 |
-
# It was a file or something that could not be iterated.
|
449 |
-
yield abs_path.replace(rs, "")
|
450 |
-
# END path exception handling
|
451 |
-
# END for each path
|
452 |
-
|
453 |
-
def _write_path_to_stdin(
|
454 |
-
self,
|
455 |
-
proc: "Popen",
|
456 |
-
filepath: PathLike,
|
457 |
-
item: PathLike,
|
458 |
-
fmakeexc: Callable[..., GitError],
|
459 |
-
fprogress: Callable[[PathLike, bool, PathLike], None],
|
460 |
-
read_from_stdout: bool = True,
|
461 |
-
) -> Union[None, str]:
|
462 |
-
"""Write path to ``proc.stdin`` and make sure it processes the item, including
|
463 |
-
progress.
|
464 |
-
|
465 |
-
:return:
|
466 |
-
stdout string
|
467 |
-
|
468 |
-
:param read_from_stdout:
|
469 |
-
If ``True``, ``proc.stdout`` will be read after the item was sent to stdin.
|
470 |
-
In that case, it will return ``None``.
|
471 |
-
|
472 |
-
:note:
|
473 |
-
There is a bug in :manpage:`git-update-index(1)` that prevents it from
|
474 |
-
sending reports just in time. This is why we have a version that tries to
|
475 |
-
read stdout and one which doesn't. In fact, the stdout is not important as
|
476 |
-
the piped-in files are processed anyway and just in time.
|
477 |
-
|
478 |
-
:note:
|
479 |
-
Newlines are essential here, git's behaviour is somewhat inconsistent on
|
480 |
-
this depending on the version, hence we try our best to deal with newlines
|
481 |
-
carefully. Usually the last newline will not be sent, instead we will close
|
482 |
-
stdin to break the pipe.
|
483 |
-
"""
|
484 |
-
fprogress(filepath, False, item)
|
485 |
-
rval: Union[None, str] = None
|
486 |
-
|
487 |
-
if proc.stdin is not None:
|
488 |
-
try:
|
489 |
-
proc.stdin.write(("%s\n" % filepath).encode(defenc))
|
490 |
-
except IOError as e:
|
491 |
-
# Pipe broke, usually because some error happened.
|
492 |
-
raise fmakeexc() from e
|
493 |
-
# END write exception handling
|
494 |
-
proc.stdin.flush()
|
495 |
-
|
496 |
-
if read_from_stdout and proc.stdout is not None:
|
497 |
-
rval = proc.stdout.readline().strip()
|
498 |
-
fprogress(filepath, True, item)
|
499 |
-
return rval
|
500 |
-
|
501 |
-
def iter_blobs(
|
502 |
-
self, predicate: Callable[[Tuple[StageType, Blob]], bool] = lambda t: True
|
503 |
-
) -> Iterator[Tuple[StageType, Blob]]:
|
504 |
-
"""
|
505 |
-
:return:
|
506 |
-
Iterator yielding tuples of :class:`~git.objects.blob.Blob` objects and
|
507 |
-
stages, tuple(stage, Blob).
|
508 |
-
|
509 |
-
:param predicate:
|
510 |
-
Function(t) returning ``True`` if tuple(stage, Blob) should be yielded by
|
511 |
-
the iterator. A default filter, the `~git.index.typ.BlobFilter`, allows you
|
512 |
-
to yield blobs only if they match a given list of paths.
|
513 |
-
"""
|
514 |
-
for entry in self.entries.values():
|
515 |
-
blob = entry.to_blob(self.repo)
|
516 |
-
blob.size = entry.size
|
517 |
-
output = (entry.stage, blob)
|
518 |
-
if predicate(output):
|
519 |
-
yield output
|
520 |
-
# END for each entry
|
521 |
-
|
522 |
-
def unmerged_blobs(self) -> Dict[PathLike, List[Tuple[StageType, Blob]]]:
|
523 |
-
"""
|
524 |
-
:return:
|
525 |
-
Dict(path : list(tuple(stage, Blob, ...))), being a dictionary associating a
|
526 |
-
path in the index with a list containing sorted stage/blob pairs.
|
527 |
-
|
528 |
-
:note:
|
529 |
-
Blobs that have been removed in one side simply do not exist in the given
|
530 |
-
stage. That is, a file removed on the 'other' branch whose entries are at
|
531 |
-
stage 3 will not have a stage 3 entry.
|
532 |
-
"""
|
533 |
-
is_unmerged_blob = lambda t: t[0] != 0
|
534 |
-
path_map: Dict[PathLike, List[Tuple[StageType, Blob]]] = {}
|
535 |
-
for stage, blob in self.iter_blobs(is_unmerged_blob):
|
536 |
-
path_map.setdefault(blob.path, []).append((stage, blob))
|
537 |
-
# END for each unmerged blob
|
538 |
-
for line in path_map.values():
|
539 |
-
line.sort()
|
540 |
-
|
541 |
-
return path_map
|
542 |
-
|
543 |
-
@classmethod
|
544 |
-
def entry_key(cls, *entry: Union[BaseIndexEntry, PathLike, StageType]) -> Tuple[PathLike, StageType]:
|
545 |
-
return entry_key(*entry)
|
546 |
-
|
547 |
-
def resolve_blobs(self, iter_blobs: Iterator[Blob]) -> "IndexFile":
|
548 |
-
"""Resolve the blobs given in blob iterator.
|
549 |
-
|
550 |
-
This will effectively remove the index entries of the respective path at all
|
551 |
-
non-null stages and add the given blob as new stage null blob.
|
552 |
-
|
553 |
-
For each path there may only be one blob, otherwise a :exc:`ValueError` will be
|
554 |
-
raised claiming the path is already at stage 0.
|
555 |
-
|
556 |
-
:raise ValueError:
|
557 |
-
If one of the blobs already existed at stage 0.
|
558 |
-
|
559 |
-
:return:
|
560 |
-
self
|
561 |
-
|
562 |
-
:note:
|
563 |
-
You will have to write the index manually once you are done, i.e.
|
564 |
-
``index.resolve_blobs(blobs).write()``.
|
565 |
-
"""
|
566 |
-
for blob in iter_blobs:
|
567 |
-
stage_null_key = (blob.path, 0)
|
568 |
-
if stage_null_key in self.entries:
|
569 |
-
raise ValueError("Path %r already exists at stage 0" % str(blob.path))
|
570 |
-
# END assert blob is not stage 0 already
|
571 |
-
|
572 |
-
# Delete all possible stages.
|
573 |
-
for stage in (1, 2, 3):
|
574 |
-
try:
|
575 |
-
del self.entries[(blob.path, stage)]
|
576 |
-
except KeyError:
|
577 |
-
pass
|
578 |
-
# END ignore key errors
|
579 |
-
# END for each possible stage
|
580 |
-
|
581 |
-
self.entries[stage_null_key] = IndexEntry.from_blob(blob)
|
582 |
-
# END for each blob
|
583 |
-
|
584 |
-
return self
|
585 |
-
|
586 |
-
def update(self) -> "IndexFile":
|
587 |
-
"""Reread the contents of our index file, discarding all cached information
|
588 |
-
we might have.
|
589 |
-
|
590 |
-
:note:
|
591 |
-
This is a possibly dangerous operations as it will discard your changes to
|
592 |
-
:attr:`index.entries <entries>`.
|
593 |
-
|
594 |
-
:return:
|
595 |
-
self
|
596 |
-
"""
|
597 |
-
self._delete_entries_cache()
|
598 |
-
# Allows to lazily reread on demand.
|
599 |
-
return self
|
600 |
-
|
601 |
-
def write_tree(self) -> Tree:
|
602 |
-
"""Write this index to a corresponding :class:`~git.objects.tree.Tree` object
|
603 |
-
into the repository's object database and return it.
|
604 |
-
|
605 |
-
:return:
|
606 |
-
:class:`~git.objects.tree.Tree` object representing this index.
|
607 |
-
|
608 |
-
:note:
|
609 |
-
The tree will be written even if one or more objects the tree refers to does
|
610 |
-
not yet exist in the object database. This could happen if you added entries
|
611 |
-
to the index directly.
|
612 |
-
|
613 |
-
:raise ValueError:
|
614 |
-
If there are no entries in the cache.
|
615 |
-
|
616 |
-
:raise git.exc.UnmergedEntriesError:
|
617 |
-
"""
|
618 |
-
# We obtain no lock as we just flush our contents to disk as tree.
|
619 |
-
# If we are a new index, the entries access will load our data accordingly.
|
620 |
-
mdb = MemoryDB()
|
621 |
-
entries = self._entries_sorted()
|
622 |
-
binsha, tree_items = write_tree_from_cache(entries, mdb, slice(0, len(entries)))
|
623 |
-
|
624 |
-
# Copy changed trees only.
|
625 |
-
mdb.stream_copy(mdb.sha_iter(), self.repo.odb)
|
626 |
-
|
627 |
-
# Note: Additional deserialization could be saved if write_tree_from_cache would
|
628 |
-
# return sorted tree entries.
|
629 |
-
root_tree = Tree(self.repo, binsha, path="")
|
630 |
-
root_tree._cache = tree_items
|
631 |
-
return root_tree
|
632 |
-
|
633 |
-
def _process_diff_args(
|
634 |
-
self,
|
635 |
-
args: List[Union[PathLike, "git_diff.Diffable"]],
|
636 |
-
) -> List[Union[PathLike, "git_diff.Diffable"]]:
|
637 |
-
try:
|
638 |
-
args.pop(args.index(self))
|
639 |
-
except IndexError:
|
640 |
-
pass
|
641 |
-
# END remove self
|
642 |
-
return args
|
643 |
-
|
644 |
-
def _to_relative_path(self, path: PathLike) -> PathLike:
|
645 |
-
"""
|
646 |
-
:return:
|
647 |
-
Version of path relative to our git directory or raise :exc:`ValueError` if
|
648 |
-
it is not within our git directory.
|
649 |
-
|
650 |
-
:raise ValueError:
|
651 |
-
"""
|
652 |
-
if not osp.isabs(path):
|
653 |
-
return path
|
654 |
-
if self.repo.bare:
|
655 |
-
raise InvalidGitRepositoryError("require non-bare repository")
|
656 |
-
if not str(path).startswith(str(self.repo.working_tree_dir)):
|
657 |
-
raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir))
|
658 |
-
return os.path.relpath(path, self.repo.working_tree_dir)
|
659 |
-
|
660 |
-
def _preprocess_add_items(
|
661 |
-
self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]
|
662 |
-
) -> Tuple[List[PathLike], List[BaseIndexEntry]]:
|
663 |
-
"""Split the items into two lists of path strings and BaseEntries."""
|
664 |
-
paths = []
|
665 |
-
entries = []
|
666 |
-
# if it is a string put in list
|
667 |
-
if isinstance(items, (str, os.PathLike)):
|
668 |
-
items = [items]
|
669 |
-
|
670 |
-
for item in items:
|
671 |
-
if isinstance(item, (str, os.PathLike)):
|
672 |
-
paths.append(self._to_relative_path(item))
|
673 |
-
elif isinstance(item, (Blob, Submodule)):
|
674 |
-
entries.append(BaseIndexEntry.from_blob(item))
|
675 |
-
elif isinstance(item, BaseIndexEntry):
|
676 |
-
entries.append(item)
|
677 |
-
else:
|
678 |
-
raise TypeError("Invalid Type: %r" % item)
|
679 |
-
# END for each item
|
680 |
-
return paths, entries
|
681 |
-
|
682 |
-
def _store_path(self, filepath: PathLike, fprogress: Callable) -> BaseIndexEntry:
|
683 |
-
"""Store file at filepath in the database and return the base index entry.
|
684 |
-
|
685 |
-
:note:
|
686 |
-
This needs the :func:`~git.index.util.git_working_dir` decorator active!
|
687 |
-
This must be ensured in the calling code.
|
688 |
-
"""
|
689 |
-
st = os.lstat(filepath) # Handles non-symlinks as well.
|
690 |
-
if S_ISLNK(st.st_mode):
|
691 |
-
# In PY3, readlink is a string, but we need bytes.
|
692 |
-
# In PY2, it was just OS encoded bytes, we assumed UTF-8.
|
693 |
-
open_stream: Callable[[], BinaryIO] = lambda: BytesIO(force_bytes(os.readlink(filepath), encoding=defenc))
|
694 |
-
else:
|
695 |
-
open_stream = lambda: open(filepath, "rb")
|
696 |
-
with open_stream() as stream:
|
697 |
-
fprogress(filepath, False, filepath)
|
698 |
-
istream = self.repo.odb.store(IStream(Blob.type, st.st_size, stream))
|
699 |
-
fprogress(filepath, True, filepath)
|
700 |
-
return BaseIndexEntry(
|
701 |
-
(
|
702 |
-
stat_mode_to_index_mode(st.st_mode),
|
703 |
-
istream.binsha,
|
704 |
-
0,
|
705 |
-
to_native_path_linux(filepath),
|
706 |
-
)
|
707 |
-
)
|
708 |
-
|
709 |
-
@unbare_repo
|
710 |
-
@git_working_dir
|
711 |
-
def _entries_for_paths(
|
712 |
-
self,
|
713 |
-
paths: List[str],
|
714 |
-
path_rewriter: Union[Callable, None],
|
715 |
-
fprogress: Callable,
|
716 |
-
entries: List[BaseIndexEntry],
|
717 |
-
) -> List[BaseIndexEntry]:
|
718 |
-
entries_added: List[BaseIndexEntry] = []
|
719 |
-
if path_rewriter:
|
720 |
-
for path in paths:
|
721 |
-
if osp.isabs(path):
|
722 |
-
abspath = path
|
723 |
-
gitrelative_path = path[len(str(self.repo.working_tree_dir)) + 1 :]
|
724 |
-
else:
|
725 |
-
gitrelative_path = path
|
726 |
-
if self.repo.working_tree_dir:
|
727 |
-
abspath = osp.join(self.repo.working_tree_dir, gitrelative_path)
|
728 |
-
# END obtain relative and absolute paths
|
729 |
-
|
730 |
-
blob = Blob(
|
731 |
-
self.repo,
|
732 |
-
Blob.NULL_BIN_SHA,
|
733 |
-
stat_mode_to_index_mode(os.stat(abspath).st_mode),
|
734 |
-
to_native_path_linux(gitrelative_path),
|
735 |
-
)
|
736 |
-
# TODO: variable undefined
|
737 |
-
entries.append(BaseIndexEntry.from_blob(blob))
|
738 |
-
# END for each path
|
739 |
-
del paths[:]
|
740 |
-
# END rewrite paths
|
741 |
-
|
742 |
-
# HANDLE PATHS
|
743 |
-
assert len(entries_added) == 0
|
744 |
-
for filepath in self._iter_expand_paths(paths):
|
745 |
-
entries_added.append(self._store_path(filepath, fprogress))
|
746 |
-
# END for each filepath
|
747 |
-
# END path handling
|
748 |
-
return entries_added
|
749 |
-
|
750 |
-
def add(
|
751 |
-
self,
|
752 |
-
items: Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]],
|
753 |
-
force: bool = True,
|
754 |
-
fprogress: Callable = lambda *args: None,
|
755 |
-
path_rewriter: Union[Callable[..., PathLike], None] = None,
|
756 |
-
write: bool = True,
|
757 |
-
write_extension_data: bool = False,
|
758 |
-
) -> List[BaseIndexEntry]:
|
759 |
-
R"""Add files from the working tree, specific blobs, or
|
760 |
-
:class:`~git.index.typ.BaseIndexEntry`\s to the index.
|
761 |
-
|
762 |
-
:param items:
|
763 |
-
Multiple types of items are supported, types can be mixed within one call.
|
764 |
-
Different types imply a different handling. File paths may generally be
|
765 |
-
relative or absolute.
|
766 |
-
|
767 |
-
- path string
|
768 |
-
|
769 |
-
Strings denote a relative or absolute path into the repository pointing
|
770 |
-
to an existing file, e.g., ``CHANGES``, `lib/myfile.ext``,
|
771 |
-
``/home/gitrepo/lib/myfile.ext``.
|
772 |
-
|
773 |
-
Absolute paths must start with working tree directory of this index's
|
774 |
-
repository to be considered valid. For example, if it was initialized
|
775 |
-
with a non-normalized path, like ``/root/repo/../repo``, absolute paths
|
776 |
-
to be added must start with ``/root/repo/../repo``.
|
777 |
-
|
778 |
-
Paths provided like this must exist. When added, they will be written
|
779 |
-
into the object database.
|
780 |
-
|
781 |
-
PathStrings may contain globs, such as ``lib/__init__*``. Or they can be
|
782 |
-
directories like ``lib``, which will add all the files within the
|
783 |
-
directory and subdirectories.
|
784 |
-
|
785 |
-
This equals a straight :manpage:`git-add(1)`.
|
786 |
-
|
787 |
-
They are added at stage 0.
|
788 |
-
|
789 |
-
- :class:~`git.objects.blob.Blob` or
|
790 |
-
:class:`~git.objects.submodule.base.Submodule` object
|
791 |
-
|
792 |
-
Blobs are added as they are assuming a valid mode is set.
|
793 |
-
|
794 |
-
The file they refer to may or may not exist in the file system, but must
|
795 |
-
be a path relative to our repository.
|
796 |
-
|
797 |
-
If their sha is null (40*0), their path must exist in the file system
|
798 |
-
relative to the git repository as an object will be created from the
|
799 |
-
data at the path.
|
800 |
-
|
801 |
-
The handling now very much equals the way string paths are processed,
|
802 |
-
except that the mode you have set will be kept. This allows you to
|
803 |
-
create symlinks by settings the mode respectively and writing the target
|
804 |
-
of the symlink directly into the file. This equals a default Linux
|
805 |
-
symlink which is not dereferenced automatically, except that it can be
|
806 |
-
created on filesystems not supporting it as well.
|
807 |
-
|
808 |
-
Please note that globs or directories are not allowed in
|
809 |
-
:class:`~git.objects.blob.Blob` objects.
|
810 |
-
|
811 |
-
They are added at stage 0.
|
812 |
-
|
813 |
-
- :class:`~git.index.typ.BaseIndexEntry` or type
|
814 |
-
|
815 |
-
Handling equals the one of :class:~`git.objects.blob.Blob` objects, but
|
816 |
-
the stage may be explicitly set. Please note that Index Entries require
|
817 |
-
binary sha's.
|
818 |
-
|
819 |
-
:param force:
|
820 |
-
**CURRENTLY INEFFECTIVE**
|
821 |
-
If ``True``, otherwise ignored or excluded files will be added anyway. As
|
822 |
-
opposed to the :manpage:`git-add(1)` command, we enable this flag by default
|
823 |
-
as the API user usually wants the item to be added even though they might be
|
824 |
-
excluded.
|
825 |
-
|
826 |
-
:param fprogress:
|
827 |
-
Function with signature ``f(path, done=False, item=item)`` called for each
|
828 |
-
path to be added, one time once it is about to be added where ``done=False``
|
829 |
-
and once after it was added where ``done=True``.
|
830 |
-
|
831 |
-
``item`` is set to the actual item we handle, either a path or a
|
832 |
-
:class:`~git.index.typ.BaseIndexEntry`.
|
833 |
-
|
834 |
-
Please note that the processed path is not guaranteed to be present in the
|
835 |
-
index already as the index is currently being processed.
|
836 |
-
|
837 |
-
:param path_rewriter:
|
838 |
-
Function, with signature ``(string) func(BaseIndexEntry)``, returning a path
|
839 |
-
for each passed entry which is the path to be actually recorded for the
|
840 |
-
object created from :attr:`entry.path <git.index.typ.BaseIndexEntry.path>`.
|
841 |
-
This allows you to write an index which is not identical to the layout of
|
842 |
-
the actual files on your hard-disk. If not ``None`` and `items` contain
|
843 |
-
plain paths, these paths will be converted to Entries beforehand and passed
|
844 |
-
to the path_rewriter. Please note that ``entry.path`` is relative to the git
|
845 |
-
repository.
|
846 |
-
|
847 |
-
:param write:
|
848 |
-
If ``True``, the index will be written once it was altered. Otherwise the
|
849 |
-
changes only exist in memory and are not available to git commands.
|
850 |
-
|
851 |
-
:param write_extension_data:
|
852 |
-
If ``True``, extension data will be written back to the index. This can lead
|
853 |
-
to issues in case it is containing the 'TREE' extension, which will cause
|
854 |
-
the :manpage:`git-commit(1)` command to write an old tree, instead of a new
|
855 |
-
one representing the now changed index.
|
856 |
-
|
857 |
-
This doesn't matter if you use :meth:`IndexFile.commit`, which ignores the
|
858 |
-
'TREE' extension altogether. You should set it to ``True`` if you intend to
|
859 |
-
use :meth:`IndexFile.commit` exclusively while maintaining support for
|
860 |
-
third-party extensions. Besides that, you can usually safely ignore the
|
861 |
-
built-in extensions when using GitPython on repositories that are not
|
862 |
-
handled manually at all.
|
863 |
-
|
864 |
-
All current built-in extensions are listed here:
|
865 |
-
https://git-scm.com/docs/index-format
|
866 |
-
|
867 |
-
:return:
|
868 |
-
List of :class:`~git.index.typ.BaseIndexEntry`\s representing the entries
|
869 |
-
just actually added.
|
870 |
-
|
871 |
-
:raise OSError:
|
872 |
-
If a supplied path did not exist. Please note that
|
873 |
-
:class:`~git.index.typ.BaseIndexEntry` objects that do not have a null sha
|
874 |
-
will be added even if their paths do not exist.
|
875 |
-
"""
|
876 |
-
# Sort the entries into strings and Entries.
|
877 |
-
# Blobs are converted to entries automatically.
|
878 |
-
# Paths can be git-added. For everything else we use git-update-index.
|
879 |
-
paths, entries = self._preprocess_add_items(items)
|
880 |
-
entries_added: List[BaseIndexEntry] = []
|
881 |
-
# This code needs a working tree, so we try not to run it unless required.
|
882 |
-
# That way, we are OK on a bare repository as well.
|
883 |
-
# If there are no paths, the rewriter has nothing to do either.
|
884 |
-
if paths:
|
885 |
-
entries_added.extend(self._entries_for_paths(paths, path_rewriter, fprogress, entries))
|
886 |
-
|
887 |
-
# HANDLE ENTRIES
|
888 |
-
if entries:
|
889 |
-
null_mode_entries = [e for e in entries if e.mode == 0]
|
890 |
-
if null_mode_entries:
|
891 |
-
raise ValueError(
|
892 |
-
"At least one Entry has a null-mode - please use index.remove to remove files for clarity"
|
893 |
-
)
|
894 |
-
# END null mode should be remove
|
895 |
-
|
896 |
-
# HANDLE ENTRY OBJECT CREATION
|
897 |
-
# Create objects if required, otherwise go with the existing shas.
|
898 |
-
null_entries_indices = [i for i, e in enumerate(entries) if e.binsha == Object.NULL_BIN_SHA]
|
899 |
-
if null_entries_indices:
|
900 |
-
|
901 |
-
@git_working_dir
|
902 |
-
def handle_null_entries(self: "IndexFile") -> None:
|
903 |
-
for ei in null_entries_indices:
|
904 |
-
null_entry = entries[ei]
|
905 |
-
new_entry = self._store_path(null_entry.path, fprogress)
|
906 |
-
|
907 |
-
# Update null entry.
|
908 |
-
entries[ei] = BaseIndexEntry(
|
909 |
-
(
|
910 |
-
null_entry.mode,
|
911 |
-
new_entry.binsha,
|
912 |
-
null_entry.stage,
|
913 |
-
null_entry.path,
|
914 |
-
)
|
915 |
-
)
|
916 |
-
# END for each entry index
|
917 |
-
|
918 |
-
# END closure
|
919 |
-
|
920 |
-
handle_null_entries(self)
|
921 |
-
# END null_entry handling
|
922 |
-
|
923 |
-
# REWRITE PATHS
|
924 |
-
# If we have to rewrite the entries, do so now, after we have generated all
|
925 |
-
# object sha's.
|
926 |
-
if path_rewriter:
|
927 |
-
for i, e in enumerate(entries):
|
928 |
-
entries[i] = BaseIndexEntry((e.mode, e.binsha, e.stage, path_rewriter(e)))
|
929 |
-
# END for each entry
|
930 |
-
# END handle path rewriting
|
931 |
-
|
932 |
-
# Just go through the remaining entries and provide progress info.
|
933 |
-
for i, entry in enumerate(entries):
|
934 |
-
progress_sent = i in null_entries_indices
|
935 |
-
if not progress_sent:
|
936 |
-
fprogress(entry.path, False, entry)
|
937 |
-
fprogress(entry.path, True, entry)
|
938 |
-
# END handle progress
|
939 |
-
# END for each entry
|
940 |
-
entries_added.extend(entries)
|
941 |
-
# END if there are base entries
|
942 |
-
|
943 |
-
# FINALIZE
|
944 |
-
# Add the new entries to this instance.
|
945 |
-
for entry in entries_added:
|
946 |
-
self.entries[(entry.path, 0)] = IndexEntry.from_base(entry)
|
947 |
-
|
948 |
-
if write:
|
949 |
-
self.write(ignore_extension_data=not write_extension_data)
|
950 |
-
# END handle write
|
951 |
-
|
952 |
-
return entries_added
|
953 |
-
|
954 |
-
def _items_to_rela_paths(
|
955 |
-
self,
|
956 |
-
items: Union[PathLike, Sequence[Union[PathLike, BaseIndexEntry, Blob, Submodule]]],
|
957 |
-
) -> List[PathLike]:
|
958 |
-
"""Returns a list of repo-relative paths from the given items which
|
959 |
-
may be absolute or relative paths, entries or blobs."""
|
960 |
-
paths = []
|
961 |
-
# If string, put in list.
|
962 |
-
if isinstance(items, (str, os.PathLike)):
|
963 |
-
items = [items]
|
964 |
-
|
965 |
-
for item in items:
|
966 |
-
if isinstance(item, (BaseIndexEntry, (Blob, Submodule))):
|
967 |
-
paths.append(self._to_relative_path(item.path))
|
968 |
-
elif isinstance(item, (str, os.PathLike)):
|
969 |
-
paths.append(self._to_relative_path(item))
|
970 |
-
else:
|
971 |
-
raise TypeError("Invalid item type: %r" % item)
|
972 |
-
# END for each item
|
973 |
-
return paths
|
974 |
-
|
975 |
-
@post_clear_cache
|
976 |
-
@default_index
|
977 |
-
def remove(
|
978 |
-
self,
|
979 |
-
items: Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]],
|
980 |
-
working_tree: bool = False,
|
981 |
-
**kwargs: Any,
|
982 |
-
) -> List[str]:
|
983 |
-
R"""Remove the given items from the index and optionally from the working tree
|
984 |
-
as well.
|
985 |
-
|
986 |
-
:param items:
|
987 |
-
Multiple types of items are supported which may be be freely mixed.
|
988 |
-
|
989 |
-
- path string
|
990 |
-
|
991 |
-
Remove the given path at all stages. If it is a directory, you must
|
992 |
-
specify the ``r=True`` keyword argument to remove all file entries below
|
993 |
-
it. If absolute paths are given, they will be converted to a path
|
994 |
-
relative to the git repository directory containing the working tree
|
995 |
-
|
996 |
-
The path string may include globs, such as ``*.c``.
|
997 |
-
|
998 |
-
- :class:~`git.objects.blob.Blob` object
|
999 |
-
|
1000 |
-
Only the path portion is used in this case.
|
1001 |
-
|
1002 |
-
- :class:`~git.index.typ.BaseIndexEntry` or compatible type
|
1003 |
-
|
1004 |
-
The only relevant information here is the path. The stage is ignored.
|
1005 |
-
|
1006 |
-
:param working_tree:
|
1007 |
-
If ``True``, the entry will also be removed from the working tree,
|
1008 |
-
physically removing the respective file. This may fail if there are
|
1009 |
-
uncommitted changes in it.
|
1010 |
-
|
1011 |
-
:param kwargs:
|
1012 |
-
Additional keyword arguments to be passed to :manpage:`git-rm(1)`, such as
|
1013 |
-
``r`` to allow recursive removal.
|
1014 |
-
|
1015 |
-
:return:
|
1016 |
-
List(path_string, ...) list of repository relative paths that have been
|
1017 |
-
removed effectively.
|
1018 |
-
|
1019 |
-
This is interesting to know in case you have provided a directory or globs.
|
1020 |
-
Paths are relative to the repository.
|
1021 |
-
"""
|
1022 |
-
args = []
|
1023 |
-
if not working_tree:
|
1024 |
-
args.append("--cached")
|
1025 |
-
args.append("--")
|
1026 |
-
|
1027 |
-
# Preprocess paths.
|
1028 |
-
paths = self._items_to_rela_paths(items)
|
1029 |
-
removed_paths = self.repo.git.rm(args, paths, **kwargs).splitlines()
|
1030 |
-
|
1031 |
-
# Process output to gain proper paths.
|
1032 |
-
# rm 'path'
|
1033 |
-
return [p[4:-1] for p in removed_paths]
|
1034 |
-
|
1035 |
-
@post_clear_cache
|
1036 |
-
@default_index
|
1037 |
-
def move(
|
1038 |
-
self,
|
1039 |
-
items: Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]],
|
1040 |
-
skip_errors: bool = False,
|
1041 |
-
**kwargs: Any,
|
1042 |
-
) -> List[Tuple[str, str]]:
|
1043 |
-
"""Rename/move the items, whereas the last item is considered the destination of
|
1044 |
-
the move operation.
|
1045 |
-
|
1046 |
-
If the destination is a file, the first item (of two) must be a file as well.
|
1047 |
-
|
1048 |
-
If the destination is a directory, it may be preceded by one or more directories
|
1049 |
-
or files.
|
1050 |
-
|
1051 |
-
The working tree will be affected in non-bare repositories.
|
1052 |
-
|
1053 |
-
:param items:
|
1054 |
-
Multiple types of items are supported, please see the :meth:`remove` method
|
1055 |
-
for reference.
|
1056 |
-
|
1057 |
-
:param skip_errors:
|
1058 |
-
If ``True``, errors such as ones resulting from missing source files will be
|
1059 |
-
skipped.
|
1060 |
-
|
1061 |
-
:param kwargs:
|
1062 |
-
Additional arguments you would like to pass to :manpage:`git-mv(1)`, such as
|
1063 |
-
``dry_run`` or ``force``.
|
1064 |
-
|
1065 |
-
:return:
|
1066 |
-
List(tuple(source_path_string, destination_path_string), ...)
|
1067 |
-
|
1068 |
-
A list of pairs, containing the source file moved as well as its actual
|
1069 |
-
destination. Relative to the repository root.
|
1070 |
-
|
1071 |
-
:raise ValueError:
|
1072 |
-
If only one item was given.
|
1073 |
-
|
1074 |
-
:raise git.exc.GitCommandError:
|
1075 |
-
If git could not handle your request.
|
1076 |
-
"""
|
1077 |
-
args = []
|
1078 |
-
if skip_errors:
|
1079 |
-
args.append("-k")
|
1080 |
-
|
1081 |
-
paths = self._items_to_rela_paths(items)
|
1082 |
-
if len(paths) < 2:
|
1083 |
-
raise ValueError("Please provide at least one source and one destination of the move operation")
|
1084 |
-
|
1085 |
-
was_dry_run = kwargs.pop("dry_run", kwargs.pop("n", None))
|
1086 |
-
kwargs["dry_run"] = True
|
1087 |
-
|
1088 |
-
# First execute rename in dry run so the command tells us what it actually does
|
1089 |
-
# (for later output).
|
1090 |
-
out = []
|
1091 |
-
mvlines = self.repo.git.mv(args, paths, **kwargs).splitlines()
|
1092 |
-
|
1093 |
-
# Parse result - first 0:n/2 lines are 'checking ', the remaining ones are the
|
1094 |
-
# 'renaming' ones which we parse.
|
1095 |
-
for ln in range(int(len(mvlines) / 2), len(mvlines)):
|
1096 |
-
tokens = mvlines[ln].split(" to ")
|
1097 |
-
assert len(tokens) == 2, "Too many tokens in %s" % mvlines[ln]
|
1098 |
-
|
1099 |
-
# [0] = Renaming x
|
1100 |
-
# [1] = y
|
1101 |
-
out.append((tokens[0][9:], tokens[1]))
|
1102 |
-
# END for each line to parse
|
1103 |
-
|
1104 |
-
# Either prepare for the real run, or output the dry-run result.
|
1105 |
-
if was_dry_run:
|
1106 |
-
return out
|
1107 |
-
# END handle dry run
|
1108 |
-
|
1109 |
-
# Now apply the actual operation.
|
1110 |
-
kwargs.pop("dry_run")
|
1111 |
-
self.repo.git.mv(args, paths, **kwargs)
|
1112 |
-
|
1113 |
-
return out
|
1114 |
-
|
1115 |
-
def commit(
|
1116 |
-
self,
|
1117 |
-
message: str,
|
1118 |
-
parent_commits: Union[List[Commit], None] = None,
|
1119 |
-
head: bool = True,
|
1120 |
-
author: Union[None, "Actor"] = None,
|
1121 |
-
committer: Union[None, "Actor"] = None,
|
1122 |
-
author_date: Union[datetime.datetime, str, None] = None,
|
1123 |
-
commit_date: Union[datetime.datetime, str, None] = None,
|
1124 |
-
skip_hooks: bool = False,
|
1125 |
-
) -> Commit:
|
1126 |
-
"""Commit the current default index file, creating a
|
1127 |
-
:class:`~git.objects.commit.Commit` object.
|
1128 |
-
|
1129 |
-
For more information on the arguments, see
|
1130 |
-
:meth:`Commit.create_from_tree <git.objects.commit.Commit.create_from_tree>`.
|
1131 |
-
|
1132 |
-
:note:
|
1133 |
-
If you have manually altered the :attr:`entries` member of this instance,
|
1134 |
-
don't forget to :meth:`write` your changes to disk beforehand.
|
1135 |
-
|
1136 |
-
:note:
|
1137 |
-
Passing ``skip_hooks=True`` is the equivalent of using ``-n`` or
|
1138 |
-
``--no-verify`` on the command line.
|
1139 |
-
|
1140 |
-
:return:
|
1141 |
-
:class:`~git.objects.commit.Commit` object representing the new commit
|
1142 |
-
"""
|
1143 |
-
if not skip_hooks:
|
1144 |
-
run_commit_hook("pre-commit", self)
|
1145 |
-
|
1146 |
-
self._write_commit_editmsg(message)
|
1147 |
-
run_commit_hook("commit-msg", self, self._commit_editmsg_filepath())
|
1148 |
-
message = self._read_commit_editmsg()
|
1149 |
-
self._remove_commit_editmsg()
|
1150 |
-
tree = self.write_tree()
|
1151 |
-
rval = Commit.create_from_tree(
|
1152 |
-
self.repo,
|
1153 |
-
tree,
|
1154 |
-
message,
|
1155 |
-
parent_commits,
|
1156 |
-
head,
|
1157 |
-
author=author,
|
1158 |
-
committer=committer,
|
1159 |
-
author_date=author_date,
|
1160 |
-
commit_date=commit_date,
|
1161 |
-
)
|
1162 |
-
if not skip_hooks:
|
1163 |
-
run_commit_hook("post-commit", self)
|
1164 |
-
return rval
|
1165 |
-
|
1166 |
-
def _write_commit_editmsg(self, message: str) -> None:
|
1167 |
-
with open(self._commit_editmsg_filepath(), "wb") as commit_editmsg_file:
|
1168 |
-
commit_editmsg_file.write(message.encode(defenc))
|
1169 |
-
|
1170 |
-
def _remove_commit_editmsg(self) -> None:
|
1171 |
-
os.remove(self._commit_editmsg_filepath())
|
1172 |
-
|
1173 |
-
def _read_commit_editmsg(self) -> str:
|
1174 |
-
with open(self._commit_editmsg_filepath(), "rb") as commit_editmsg_file:
|
1175 |
-
return commit_editmsg_file.read().decode(defenc)
|
1176 |
-
|
1177 |
-
def _commit_editmsg_filepath(self) -> str:
|
1178 |
-
return osp.join(self.repo.common_dir, "COMMIT_EDITMSG")
|
1179 |
-
|
1180 |
-
def _flush_stdin_and_wait(cls, proc: "Popen[bytes]", ignore_stdout: bool = False) -> bytes:
|
1181 |
-
stdin_IO = proc.stdin
|
1182 |
-
if stdin_IO:
|
1183 |
-
stdin_IO.flush()
|
1184 |
-
stdin_IO.close()
|
1185 |
-
|
1186 |
-
stdout = b""
|
1187 |
-
if not ignore_stdout and proc.stdout:
|
1188 |
-
stdout = proc.stdout.read()
|
1189 |
-
|
1190 |
-
if proc.stdout:
|
1191 |
-
proc.stdout.close()
|
1192 |
-
proc.wait()
|
1193 |
-
return stdout
|
1194 |
-
|
1195 |
-
@default_index
|
1196 |
-
def checkout(
|
1197 |
-
self,
|
1198 |
-
paths: Union[None, Iterable[PathLike]] = None,
|
1199 |
-
force: bool = False,
|
1200 |
-
fprogress: Callable = lambda *args: None,
|
1201 |
-
**kwargs: Any,
|
1202 |
-
) -> Union[None, Iterator[PathLike], Sequence[PathLike]]:
|
1203 |
-
"""Check out the given paths or all files from the version known to the index
|
1204 |
-
into the working tree.
|
1205 |
-
|
1206 |
-
:note:
|
1207 |
-
Be sure you have written pending changes using the :meth:`write` method in
|
1208 |
-
case you have altered the entries dictionary directly.
|
1209 |
-
|
1210 |
-
:param paths:
|
1211 |
-
If ``None``, all paths in the index will be checked out.
|
1212 |
-
Otherwise an iterable of relative or absolute paths or a single path
|
1213 |
-
pointing to files or directories in the index is expected.
|
1214 |
-
|
1215 |
-
:param force:
|
1216 |
-
If ``True``, existing files will be overwritten even if they contain local
|
1217 |
-
modifications.
|
1218 |
-
If ``False``, these will trigger a :exc:`~git.exc.CheckoutError`.
|
1219 |
-
|
1220 |
-
:param fprogress:
|
1221 |
-
See :meth:`IndexFile.add` for signature and explanation.
|
1222 |
-
|
1223 |
-
The provided progress information will contain ``None`` as path and item if
|
1224 |
-
no explicit paths are given. Otherwise progress information will be send
|
1225 |
-
prior and after a file has been checked out.
|
1226 |
-
|
1227 |
-
:param kwargs:
|
1228 |
-
Additional arguments to be passed to :manpage:`git-checkout-index(1)`.
|
1229 |
-
|
1230 |
-
:return:
|
1231 |
-
Iterable yielding paths to files which have been checked out and are
|
1232 |
-
guaranteed to match the version stored in the index.
|
1233 |
-
|
1234 |
-
:raise git.exc.CheckoutError:
|
1235 |
-
* If at least one file failed to be checked out. This is a summary, hence it
|
1236 |
-
will checkout as many files as it can anyway.
|
1237 |
-
* If one of files or directories do not exist in the index (as opposed to
|
1238 |
-
the original git command, which ignores them).
|
1239 |
-
|
1240 |
-
:raise git.exc.GitCommandError:
|
1241 |
-
If error lines could not be parsed - this truly is an exceptional state.
|
1242 |
-
|
1243 |
-
:note:
|
1244 |
-
The checkout is limited to checking out the files in the index. Files which
|
1245 |
-
are not in the index anymore and exist in the working tree will not be
|
1246 |
-
deleted. This behaviour is fundamentally different to ``head.checkout``,
|
1247 |
-
i.e. if you want :manpage:`git-checkout(1)`-like behaviour, use
|
1248 |
-
``head.checkout`` instead of ``index.checkout``.
|
1249 |
-
"""
|
1250 |
-
args = ["--index"]
|
1251 |
-
if force:
|
1252 |
-
args.append("--force")
|
1253 |
-
|
1254 |
-
failed_files = []
|
1255 |
-
failed_reasons = []
|
1256 |
-
unknown_lines = []
|
1257 |
-
|
1258 |
-
def handle_stderr(proc: "Popen[bytes]", iter_checked_out_files: Iterable[PathLike]) -> None:
|
1259 |
-
stderr_IO = proc.stderr
|
1260 |
-
if not stderr_IO:
|
1261 |
-
return # Return early if stderr empty.
|
1262 |
-
|
1263 |
-
stderr_bytes = stderr_IO.read()
|
1264 |
-
# line contents:
|
1265 |
-
stderr = stderr_bytes.decode(defenc)
|
1266 |
-
# git-checkout-index: this already exists
|
1267 |
-
endings = (
|
1268 |
-
" already exists",
|
1269 |
-
" is not in the cache",
|
1270 |
-
" does not exist at stage",
|
1271 |
-
" is unmerged",
|
1272 |
-
)
|
1273 |
-
for line in stderr.splitlines():
|
1274 |
-
if not line.startswith("git checkout-index: ") and not line.startswith("git-checkout-index: "):
|
1275 |
-
is_a_dir = " is a directory"
|
1276 |
-
unlink_issue = "unable to unlink old '"
|
1277 |
-
already_exists_issue = " already exists, no checkout" # created by entry.c:checkout_entry(...)
|
1278 |
-
if line.endswith(is_a_dir):
|
1279 |
-
failed_files.append(line[: -len(is_a_dir)])
|
1280 |
-
failed_reasons.append(is_a_dir)
|
1281 |
-
elif line.startswith(unlink_issue):
|
1282 |
-
failed_files.append(line[len(unlink_issue) : line.rfind("'")])
|
1283 |
-
failed_reasons.append(unlink_issue)
|
1284 |
-
elif line.endswith(already_exists_issue):
|
1285 |
-
failed_files.append(line[: -len(already_exists_issue)])
|
1286 |
-
failed_reasons.append(already_exists_issue)
|
1287 |
-
else:
|
1288 |
-
unknown_lines.append(line)
|
1289 |
-
continue
|
1290 |
-
# END special lines parsing
|
1291 |
-
|
1292 |
-
for e in endings:
|
1293 |
-
if line.endswith(e):
|
1294 |
-
failed_files.append(line[20 : -len(e)])
|
1295 |
-
failed_reasons.append(e)
|
1296 |
-
break
|
1297 |
-
# END if ending matches
|
1298 |
-
# END for each possible ending
|
1299 |
-
# END for each line
|
1300 |
-
if unknown_lines:
|
1301 |
-
raise GitCommandError(("git-checkout-index",), 128, stderr)
|
1302 |
-
if failed_files:
|
1303 |
-
valid_files = list(set(iter_checked_out_files) - set(failed_files))
|
1304 |
-
raise CheckoutError(
|
1305 |
-
"Some files could not be checked out from the index due to local modifications",
|
1306 |
-
failed_files,
|
1307 |
-
valid_files,
|
1308 |
-
failed_reasons,
|
1309 |
-
)
|
1310 |
-
|
1311 |
-
# END stderr handler
|
1312 |
-
|
1313 |
-
if paths is None:
|
1314 |
-
args.append("--all")
|
1315 |
-
kwargs["as_process"] = 1
|
1316 |
-
fprogress(None, False, None)
|
1317 |
-
proc = self.repo.git.checkout_index(*args, **kwargs)
|
1318 |
-
proc.wait()
|
1319 |
-
fprogress(None, True, None)
|
1320 |
-
rval_iter = (e.path for e in self.entries.values())
|
1321 |
-
handle_stderr(proc, rval_iter)
|
1322 |
-
return rval_iter
|
1323 |
-
else:
|
1324 |
-
if isinstance(paths, str):
|
1325 |
-
paths = [paths]
|
1326 |
-
|
1327 |
-
# Make sure we have our entries loaded before we start checkout_index, which
|
1328 |
-
# will hold a lock on it. We try to get the lock as well during our entries
|
1329 |
-
# initialization.
|
1330 |
-
self.entries # noqa: B018
|
1331 |
-
|
1332 |
-
args.append("--stdin")
|
1333 |
-
kwargs["as_process"] = True
|
1334 |
-
kwargs["istream"] = subprocess.PIPE
|
1335 |
-
proc = self.repo.git.checkout_index(args, **kwargs)
|
1336 |
-
# FIXME: Reading from GIL!
|
1337 |
-
make_exc = lambda: GitCommandError(("git-checkout-index",) + tuple(args), 128, proc.stderr.read())
|
1338 |
-
checked_out_files: List[PathLike] = []
|
1339 |
-
|
1340 |
-
for path in paths:
|
1341 |
-
co_path = to_native_path_linux(self._to_relative_path(path))
|
1342 |
-
# If the item is not in the index, it could be a directory.
|
1343 |
-
path_is_directory = False
|
1344 |
-
|
1345 |
-
try:
|
1346 |
-
self.entries[(co_path, 0)]
|
1347 |
-
except KeyError:
|
1348 |
-
folder = str(co_path)
|
1349 |
-
if not folder.endswith("/"):
|
1350 |
-
folder += "/"
|
1351 |
-
for entry in self.entries.values():
|
1352 |
-
if str(entry.path).startswith(folder):
|
1353 |
-
p = entry.path
|
1354 |
-
self._write_path_to_stdin(proc, p, p, make_exc, fprogress, read_from_stdout=False)
|
1355 |
-
checked_out_files.append(p)
|
1356 |
-
path_is_directory = True
|
1357 |
-
# END if entry is in directory
|
1358 |
-
# END for each entry
|
1359 |
-
# END path exception handlnig
|
1360 |
-
|
1361 |
-
if not path_is_directory:
|
1362 |
-
self._write_path_to_stdin(proc, co_path, path, make_exc, fprogress, read_from_stdout=False)
|
1363 |
-
checked_out_files.append(co_path)
|
1364 |
-
# END path is a file
|
1365 |
-
# END for each path
|
1366 |
-
try:
|
1367 |
-
self._flush_stdin_and_wait(proc, ignore_stdout=True)
|
1368 |
-
except GitCommandError:
|
1369 |
-
# Without parsing stdout we don't know what failed.
|
1370 |
-
raise CheckoutError( # noqa: B904
|
1371 |
-
"Some files could not be checked out from the index, probably because they didn't exist.",
|
1372 |
-
failed_files,
|
1373 |
-
[],
|
1374 |
-
failed_reasons,
|
1375 |
-
)
|
1376 |
-
|
1377 |
-
handle_stderr(proc, checked_out_files)
|
1378 |
-
return checked_out_files
|
1379 |
-
# END paths handling
|
1380 |
-
|
1381 |
-
@default_index
|
1382 |
-
def reset(
|
1383 |
-
self,
|
1384 |
-
commit: Union[Commit, "Reference", str] = "HEAD",
|
1385 |
-
working_tree: bool = False,
|
1386 |
-
paths: Union[None, Iterable[PathLike]] = None,
|
1387 |
-
head: bool = False,
|
1388 |
-
**kwargs: Any,
|
1389 |
-
) -> "IndexFile":
|
1390 |
-
"""Reset the index to reflect the tree at the given commit. This will not adjust
|
1391 |
-
our HEAD reference by default, as opposed to
|
1392 |
-
:meth:`HEAD.reset <git.refs.head.HEAD.reset>`.
|
1393 |
-
|
1394 |
-
:param commit:
|
1395 |
-
Revision, :class:`~git.refs.reference.Reference` or
|
1396 |
-
:class:`~git.objects.commit.Commit` specifying the commit we should
|
1397 |
-
represent.
|
1398 |
-
|
1399 |
-
If you want to specify a tree only, use :meth:`IndexFile.from_tree` and
|
1400 |
-
overwrite the default index.
|
1401 |
-
|
1402 |
-
:param working_tree:
|
1403 |
-
If ``True``, the files in the working tree will reflect the changed index.
|
1404 |
-
If ``False``, the working tree will not be touched.
|
1405 |
-
Please note that changes to the working copy will be discarded without
|
1406 |
-
warning!
|
1407 |
-
|
1408 |
-
:param head:
|
1409 |
-
If ``True``, the head will be set to the given commit. This is ``False`` by
|
1410 |
-
default, but if ``True``, this method behaves like
|
1411 |
-
:meth:`HEAD.reset <git.refs.head.HEAD.reset>`.
|
1412 |
-
|
1413 |
-
:param paths:
|
1414 |
-
If given as an iterable of absolute or repository-relative paths, only these
|
1415 |
-
will be reset to their state at the given commit-ish.
|
1416 |
-
The paths need to exist at the commit, otherwise an exception will be
|
1417 |
-
raised.
|
1418 |
-
|
1419 |
-
:param kwargs:
|
1420 |
-
Additional keyword arguments passed to :manpage:`git-reset(1)`.
|
1421 |
-
|
1422 |
-
:note:
|
1423 |
-
:meth:`IndexFile.reset`, as opposed to
|
1424 |
-
:meth:`HEAD.reset <git.refs.head.HEAD.reset>`, will not delete any files in
|
1425 |
-
order to maintain a consistent working tree. Instead, it will just check out
|
1426 |
-
the files according to their state in the index.
|
1427 |
-
If you want :manpage:`git-reset(1)`-like behaviour, use
|
1428 |
-
:meth:`HEAD.reset <git.refs.head.HEAD.reset>` instead.
|
1429 |
-
|
1430 |
-
:return:
|
1431 |
-
self
|
1432 |
-
"""
|
1433 |
-
# What we actually want to do is to merge the tree into our existing index,
|
1434 |
-
# which is what git-read-tree does.
|
1435 |
-
new_inst = type(self).from_tree(self.repo, commit)
|
1436 |
-
if not paths:
|
1437 |
-
self.entries = new_inst.entries
|
1438 |
-
else:
|
1439 |
-
nie = new_inst.entries
|
1440 |
-
for path in paths:
|
1441 |
-
path = self._to_relative_path(path)
|
1442 |
-
try:
|
1443 |
-
key = entry_key(path, 0)
|
1444 |
-
self.entries[key] = nie[key]
|
1445 |
-
except KeyError:
|
1446 |
-
# If key is not in theirs, it musn't be in ours.
|
1447 |
-
try:
|
1448 |
-
del self.entries[key]
|
1449 |
-
except KeyError:
|
1450 |
-
pass
|
1451 |
-
# END handle deletion keyerror
|
1452 |
-
# END handle keyerror
|
1453 |
-
# END for each path
|
1454 |
-
# END handle paths
|
1455 |
-
self.write()
|
1456 |
-
|
1457 |
-
if working_tree:
|
1458 |
-
self.checkout(paths=paths, force=True)
|
1459 |
-
# END handle working tree
|
1460 |
-
|
1461 |
-
if head:
|
1462 |
-
self.repo.head.set_commit(self.repo.commit(commit), logmsg="%s: Updating HEAD" % commit)
|
1463 |
-
# END handle head change
|
1464 |
-
|
1465 |
-
return self
|
1466 |
-
|
1467 |
-
# FIXME: This is documented to accept the same parameters as Diffable.diff, but this
|
1468 |
-
# does not handle NULL_TREE for `other`. (The suppressed mypy error is about this.)
|
1469 |
-
def diff(
|
1470 |
-
self,
|
1471 |
-
other: Union[ # type: ignore[override]
|
1472 |
-
Literal[git_diff.DiffConstants.INDEX],
|
1473 |
-
"Tree",
|
1474 |
-
"Commit",
|
1475 |
-
str,
|
1476 |
-
None,
|
1477 |
-
] = git_diff.INDEX,
|
1478 |
-
paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
|
1479 |
-
create_patch: bool = False,
|
1480 |
-
**kwargs: Any,
|
1481 |
-
) -> git_diff.DiffIndex:
|
1482 |
-
"""Diff this index against the working copy or a :class:`~git.objects.tree.Tree`
|
1483 |
-
or :class:`~git.objects.commit.Commit` object.
|
1484 |
-
|
1485 |
-
For documentation of the parameters and return values, see
|
1486 |
-
:meth:`Diffable.diff <git.diff.Diffable.diff>`.
|
1487 |
-
|
1488 |
-
:note:
|
1489 |
-
Will only work with indices that represent the default git index as they
|
1490 |
-
have not been initialized with a stream.
|
1491 |
-
"""
|
1492 |
-
# Only run if we are the default repository index.
|
1493 |
-
if self._file_path != self._index_path():
|
1494 |
-
raise AssertionError("Cannot call %r on indices that do not represent the default git index" % self.diff())
|
1495 |
-
# Index against index is always empty.
|
1496 |
-
if other is self.INDEX:
|
1497 |
-
return git_diff.DiffIndex()
|
1498 |
-
|
1499 |
-
# Index against anything but None is a reverse diff with the respective item.
|
1500 |
-
# Handle existing -R flags properly.
|
1501 |
-
# Transform strings to the object so that we can call diff on it.
|
1502 |
-
if isinstance(other, str):
|
1503 |
-
other = self.repo.rev_parse(other)
|
1504 |
-
# END object conversion
|
1505 |
-
|
1506 |
-
if isinstance(other, Object): # For Tree or Commit.
|
1507 |
-
# Invert the existing R flag.
|
1508 |
-
cur_val = kwargs.get("R", False)
|
1509 |
-
kwargs["R"] = not cur_val
|
1510 |
-
return other.diff(self.INDEX, paths, create_patch, **kwargs)
|
1511 |
-
# END diff against other item handling
|
1512 |
-
|
1513 |
-
# If other is not None here, something is wrong.
|
1514 |
-
if other is not None:
|
1515 |
-
raise ValueError("other must be None, Diffable.INDEX, a Tree or Commit, was %r" % other)
|
1516 |
-
|
1517 |
-
# Diff against working copy - can be handled by superclass natively.
|
1518 |
-
return super().diff(other, paths, create_patch, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/index/fun.py
DELETED
@@ -1,465 +0,0 @@
|
|
1 |
-
# This module is part of GitPython and is released under the
|
2 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
3 |
-
|
4 |
-
"""Standalone functions to accompany the index implementation and make it more
|
5 |
-
versatile."""
|
6 |
-
|
7 |
-
__all__ = [
|
8 |
-
"write_cache",
|
9 |
-
"read_cache",
|
10 |
-
"write_tree_from_cache",
|
11 |
-
"entry_key",
|
12 |
-
"stat_mode_to_index_mode",
|
13 |
-
"S_IFGITLINK",
|
14 |
-
"run_commit_hook",
|
15 |
-
"hook_path",
|
16 |
-
]
|
17 |
-
|
18 |
-
from io import BytesIO
|
19 |
-
import os
|
20 |
-
import os.path as osp
|
21 |
-
from pathlib import Path
|
22 |
-
from stat import S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_ISDIR, S_ISLNK, S_IXUSR
|
23 |
-
import subprocess
|
24 |
-
import sys
|
25 |
-
|
26 |
-
from gitdb.base import IStream
|
27 |
-
from gitdb.typ import str_tree_type
|
28 |
-
|
29 |
-
from git.cmd import handle_process_output, safer_popen
|
30 |
-
from git.compat import defenc, force_bytes, force_text, safe_decode
|
31 |
-
from git.exc import HookExecutionError, UnmergedEntriesError
|
32 |
-
from git.objects.fun import (
|
33 |
-
traverse_tree_recursive,
|
34 |
-
traverse_trees_recursive,
|
35 |
-
tree_to_stream,
|
36 |
-
)
|
37 |
-
from git.util import IndexFileSHA1Writer, finalize_process
|
38 |
-
|
39 |
-
from .typ import BaseIndexEntry, IndexEntry, CE_NAMEMASK, CE_STAGESHIFT
|
40 |
-
from .util import pack, unpack
|
41 |
-
|
42 |
-
# typing -----------------------------------------------------------------------------
|
43 |
-
|
44 |
-
from typing import Dict, IO, List, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast
|
45 |
-
|
46 |
-
from git.types import PathLike
|
47 |
-
|
48 |
-
if TYPE_CHECKING:
|
49 |
-
from git.db import GitCmdObjectDB
|
50 |
-
from git.objects.tree import TreeCacheTup
|
51 |
-
|
52 |
-
from .base import IndexFile
|
53 |
-
|
54 |
-
# ------------------------------------------------------------------------------------
|
55 |
-
|
56 |
-
S_IFGITLINK = S_IFLNK | S_IFDIR
|
57 |
-
"""Flags for a submodule."""
|
58 |
-
|
59 |
-
CE_NAMEMASK_INV = ~CE_NAMEMASK
|
60 |
-
|
61 |
-
|
62 |
-
def hook_path(name: str, git_dir: PathLike) -> str:
|
63 |
-
""":return: path to the given named hook in the given git repository directory"""
|
64 |
-
return osp.join(git_dir, "hooks", name)
|
65 |
-
|
66 |
-
|
67 |
-
def _has_file_extension(path: str) -> str:
|
68 |
-
return osp.splitext(path)[1]
|
69 |
-
|
70 |
-
|
71 |
-
def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
|
72 |
-
"""Run the commit hook of the given name. Silently ignore hooks that do not exist.
|
73 |
-
|
74 |
-
:param name:
|
75 |
-
Name of hook, like ``pre-commit``.
|
76 |
-
|
77 |
-
:param index:
|
78 |
-
:class:`~git.index.base.IndexFile` instance.
|
79 |
-
|
80 |
-
:param args:
|
81 |
-
Arguments passed to hook file.
|
82 |
-
|
83 |
-
:raise git.exc.HookExecutionError:
|
84 |
-
"""
|
85 |
-
hp = hook_path(name, index.repo.git_dir)
|
86 |
-
if not os.access(hp, os.X_OK):
|
87 |
-
return
|
88 |
-
|
89 |
-
env = os.environ.copy()
|
90 |
-
env["GIT_INDEX_FILE"] = safe_decode(str(index.path))
|
91 |
-
env["GIT_EDITOR"] = ":"
|
92 |
-
cmd = [hp]
|
93 |
-
try:
|
94 |
-
if sys.platform == "win32" and not _has_file_extension(hp):
|
95 |
-
# Windows only uses extensions to determine how to open files
|
96 |
-
# (doesn't understand shebangs). Try using bash to run the hook.
|
97 |
-
relative_hp = Path(hp).relative_to(index.repo.working_dir).as_posix()
|
98 |
-
cmd = ["bash.exe", relative_hp]
|
99 |
-
|
100 |
-
process = safer_popen(
|
101 |
-
cmd + list(args),
|
102 |
-
env=env,
|
103 |
-
stdout=subprocess.PIPE,
|
104 |
-
stderr=subprocess.PIPE,
|
105 |
-
cwd=index.repo.working_dir,
|
106 |
-
)
|
107 |
-
except Exception as ex:
|
108 |
-
raise HookExecutionError(hp, ex) from ex
|
109 |
-
else:
|
110 |
-
stdout_list: List[str] = []
|
111 |
-
stderr_list: List[str] = []
|
112 |
-
handle_process_output(process, stdout_list.append, stderr_list.append, finalize_process)
|
113 |
-
stdout = "".join(stdout_list)
|
114 |
-
stderr = "".join(stderr_list)
|
115 |
-
if process.returncode != 0:
|
116 |
-
stdout = force_text(stdout, defenc)
|
117 |
-
stderr = force_text(stderr, defenc)
|
118 |
-
raise HookExecutionError(hp, process.returncode, stderr, stdout)
|
119 |
-
# END handle return code
|
120 |
-
|
121 |
-
|
122 |
-
def stat_mode_to_index_mode(mode: int) -> int:
|
123 |
-
"""Convert the given mode from a stat call to the corresponding index mode and
|
124 |
-
return it."""
|
125 |
-
if S_ISLNK(mode): # symlinks
|
126 |
-
return S_IFLNK
|
127 |
-
if S_ISDIR(mode) or S_IFMT(mode) == S_IFGITLINK: # submodules
|
128 |
-
return S_IFGITLINK
|
129 |
-
return S_IFREG | (mode & S_IXUSR and 0o755 or 0o644) # blobs with or without executable bit
|
130 |
-
|
131 |
-
|
132 |
-
def write_cache(
|
133 |
-
entries: Sequence[Union[BaseIndexEntry, "IndexEntry"]],
|
134 |
-
stream: IO[bytes],
|
135 |
-
extension_data: Union[None, bytes] = None,
|
136 |
-
ShaStreamCls: Type[IndexFileSHA1Writer] = IndexFileSHA1Writer,
|
137 |
-
) -> None:
|
138 |
-
"""Write the cache represented by entries to a stream.
|
139 |
-
|
140 |
-
:param entries:
|
141 |
-
**Sorted** list of entries.
|
142 |
-
|
143 |
-
:param stream:
|
144 |
-
Stream to wrap into the AdapterStreamCls - it is used for final output.
|
145 |
-
|
146 |
-
:param ShaStreamCls:
|
147 |
-
Type to use when writing to the stream. It produces a sha while writing to it,
|
148 |
-
before the data is passed on to the wrapped stream.
|
149 |
-
|
150 |
-
:param extension_data:
|
151 |
-
Any kind of data to write as a trailer, it must begin a 4 byte identifier,
|
152 |
-
followed by its size (4 bytes).
|
153 |
-
"""
|
154 |
-
# Wrap the stream into a compatible writer.
|
155 |
-
stream_sha = ShaStreamCls(stream)
|
156 |
-
|
157 |
-
tell = stream_sha.tell
|
158 |
-
write = stream_sha.write
|
159 |
-
|
160 |
-
# Header
|
161 |
-
version = 2
|
162 |
-
write(b"DIRC")
|
163 |
-
write(pack(">LL", version, len(entries)))
|
164 |
-
|
165 |
-
# Body
|
166 |
-
for entry in entries:
|
167 |
-
beginoffset = tell()
|
168 |
-
write(entry.ctime_bytes) # ctime
|
169 |
-
write(entry.mtime_bytes) # mtime
|
170 |
-
path_str = str(entry.path)
|
171 |
-
path: bytes = force_bytes(path_str, encoding=defenc)
|
172 |
-
plen = len(path) & CE_NAMEMASK # Path length
|
173 |
-
assert plen == len(path), "Path %s too long to fit into index" % entry.path
|
174 |
-
flags = plen | (entry.flags & CE_NAMEMASK_INV) # Clear possible previous values.
|
175 |
-
write(
|
176 |
-
pack(
|
177 |
-
">LLLLLL20sH",
|
178 |
-
entry.dev,
|
179 |
-
entry.inode,
|
180 |
-
entry.mode,
|
181 |
-
entry.uid,
|
182 |
-
entry.gid,
|
183 |
-
entry.size,
|
184 |
-
entry.binsha,
|
185 |
-
flags,
|
186 |
-
)
|
187 |
-
)
|
188 |
-
write(path)
|
189 |
-
real_size = (tell() - beginoffset + 8) & ~7
|
190 |
-
write(b"\0" * ((beginoffset + real_size) - tell()))
|
191 |
-
# END for each entry
|
192 |
-
|
193 |
-
# Write previously cached extensions data.
|
194 |
-
if extension_data is not None:
|
195 |
-
stream_sha.write(extension_data)
|
196 |
-
|
197 |
-
# Write the sha over the content.
|
198 |
-
stream_sha.write_sha()
|
199 |
-
|
200 |
-
|
201 |
-
def read_header(stream: IO[bytes]) -> Tuple[int, int]:
|
202 |
-
"""Return tuple(version_long, num_entries) from the given stream."""
|
203 |
-
type_id = stream.read(4)
|
204 |
-
if type_id != b"DIRC":
|
205 |
-
raise AssertionError("Invalid index file header: %r" % type_id)
|
206 |
-
unpacked = cast(Tuple[int, int], unpack(">LL", stream.read(4 * 2)))
|
207 |
-
version, num_entries = unpacked
|
208 |
-
|
209 |
-
# TODO: Handle version 3: extended data, see read-cache.c.
|
210 |
-
assert version in (1, 2)
|
211 |
-
return version, num_entries
|
212 |
-
|
213 |
-
|
214 |
-
def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, int]:
|
215 |
-
"""
|
216 |
-
:return:
|
217 |
-
Key suitable to be used for the
|
218 |
-
:attr:`index.entries <git.index.base.IndexFile.entries>` dictionary.
|
219 |
-
|
220 |
-
:param entry:
|
221 |
-
One instance of type BaseIndexEntry or the path and the stage.
|
222 |
-
"""
|
223 |
-
|
224 |
-
# def is_entry_key_tup(entry_key: Tuple) -> TypeGuard[Tuple[PathLike, int]]:
|
225 |
-
# return isinstance(entry_key, tuple) and len(entry_key) == 2
|
226 |
-
|
227 |
-
if len(entry) == 1:
|
228 |
-
entry_first = entry[0]
|
229 |
-
assert isinstance(entry_first, BaseIndexEntry)
|
230 |
-
return (entry_first.path, entry_first.stage)
|
231 |
-
else:
|
232 |
-
# assert is_entry_key_tup(entry)
|
233 |
-
entry = cast(Tuple[PathLike, int], entry)
|
234 |
-
return entry
|
235 |
-
# END handle entry
|
236 |
-
|
237 |
-
|
238 |
-
def read_cache(
|
239 |
-
stream: IO[bytes],
|
240 |
-
) -> Tuple[int, Dict[Tuple[PathLike, int], "IndexEntry"], bytes, bytes]:
|
241 |
-
"""Read a cache file from the given stream.
|
242 |
-
|
243 |
-
:return:
|
244 |
-
tuple(version, entries_dict, extension_data, content_sha)
|
245 |
-
|
246 |
-
* *version* is the integer version number.
|
247 |
-
* *entries_dict* is a dictionary which maps IndexEntry instances to a path at a
|
248 |
-
stage.
|
249 |
-
* *extension_data* is ``""`` or 4 bytes of type + 4 bytes of size + size bytes.
|
250 |
-
* *content_sha* is a 20 byte sha on all cache file contents.
|
251 |
-
"""
|
252 |
-
version, num_entries = read_header(stream)
|
253 |
-
count = 0
|
254 |
-
entries: Dict[Tuple[PathLike, int], "IndexEntry"] = {}
|
255 |
-
|
256 |
-
read = stream.read
|
257 |
-
tell = stream.tell
|
258 |
-
while count < num_entries:
|
259 |
-
beginoffset = tell()
|
260 |
-
ctime = unpack(">8s", read(8))[0]
|
261 |
-
mtime = unpack(">8s", read(8))[0]
|
262 |
-
(dev, ino, mode, uid, gid, size, sha, flags) = unpack(">LLLLLL20sH", read(20 + 4 * 6 + 2))
|
263 |
-
path_size = flags & CE_NAMEMASK
|
264 |
-
path = read(path_size).decode(defenc)
|
265 |
-
|
266 |
-
real_size = (tell() - beginoffset + 8) & ~7
|
267 |
-
read((beginoffset + real_size) - tell())
|
268 |
-
entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size))
|
269 |
-
# entry_key would be the method to use, but we save the effort.
|
270 |
-
entries[(path, entry.stage)] = entry
|
271 |
-
count += 1
|
272 |
-
# END for each entry
|
273 |
-
|
274 |
-
# The footer contains extension data and a sha on the content so far.
|
275 |
-
# Keep the extension footer,and verify we have a sha in the end.
|
276 |
-
# Extension data format is:
|
277 |
-
# 4 bytes ID
|
278 |
-
# 4 bytes length of chunk
|
279 |
-
# Repeated 0 - N times
|
280 |
-
extension_data = stream.read(~0)
|
281 |
-
assert len(extension_data) > 19, (
|
282 |
-
"Index Footer was not at least a sha on content as it was only %i bytes in size" % len(extension_data)
|
283 |
-
)
|
284 |
-
|
285 |
-
content_sha = extension_data[-20:]
|
286 |
-
|
287 |
-
# Truncate the sha in the end as we will dynamically create it anyway.
|
288 |
-
extension_data = extension_data[:-20]
|
289 |
-
|
290 |
-
return (version, entries, extension_data, content_sha)
|
291 |
-
|
292 |
-
|
293 |
-
def write_tree_from_cache(
|
294 |
-
entries: List[IndexEntry], odb: "GitCmdObjectDB", sl: slice, si: int = 0
|
295 |
-
) -> Tuple[bytes, List["TreeCacheTup"]]:
|
296 |
-
R"""Create a tree from the given sorted list of entries and put the respective
|
297 |
-
trees into the given object database.
|
298 |
-
|
299 |
-
:param entries:
|
300 |
-
**Sorted** list of :class:`~git.index.typ.IndexEntry`\s.
|
301 |
-
|
302 |
-
:param odb:
|
303 |
-
Object database to store the trees in.
|
304 |
-
|
305 |
-
:param si:
|
306 |
-
Start index at which we should start creating subtrees.
|
307 |
-
|
308 |
-
:param sl:
|
309 |
-
Slice indicating the range we should process on the entries list.
|
310 |
-
|
311 |
-
:return:
|
312 |
-
tuple(binsha, list(tree_entry, ...))
|
313 |
-
|
314 |
-
A tuple of a sha and a list of tree entries being a tuple of hexsha, mode, name.
|
315 |
-
"""
|
316 |
-
tree_items: List["TreeCacheTup"] = []
|
317 |
-
|
318 |
-
ci = sl.start
|
319 |
-
end = sl.stop
|
320 |
-
while ci < end:
|
321 |
-
entry = entries[ci]
|
322 |
-
if entry.stage != 0:
|
323 |
-
raise UnmergedEntriesError(entry)
|
324 |
-
# END abort on unmerged
|
325 |
-
ci += 1
|
326 |
-
rbound = entry.path.find("/", si)
|
327 |
-
if rbound == -1:
|
328 |
-
# It's not a tree.
|
329 |
-
tree_items.append((entry.binsha, entry.mode, entry.path[si:]))
|
330 |
-
else:
|
331 |
-
# Find common base range.
|
332 |
-
base = entry.path[si:rbound]
|
333 |
-
xi = ci
|
334 |
-
while xi < end:
|
335 |
-
oentry = entries[xi]
|
336 |
-
orbound = oentry.path.find("/", si)
|
337 |
-
if orbound == -1 or oentry.path[si:orbound] != base:
|
338 |
-
break
|
339 |
-
# END abort on base mismatch
|
340 |
-
xi += 1
|
341 |
-
# END find common base
|
342 |
-
|
343 |
-
# Enter recursion.
|
344 |
-
# ci - 1 as we want to count our current item as well.
|
345 |
-
sha, _tree_entry_list = write_tree_from_cache(entries, odb, slice(ci - 1, xi), rbound + 1)
|
346 |
-
tree_items.append((sha, S_IFDIR, base))
|
347 |
-
|
348 |
-
# Skip ahead.
|
349 |
-
ci = xi
|
350 |
-
# END handle bounds
|
351 |
-
# END for each entry
|
352 |
-
|
353 |
-
# Finally create the tree.
|
354 |
-
sio = BytesIO()
|
355 |
-
tree_to_stream(tree_items, sio.write) # Writes to stream as bytes, but doesn't change tree_items.
|
356 |
-
sio.seek(0)
|
357 |
-
|
358 |
-
istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio))
|
359 |
-
return (istream.binsha, tree_items)
|
360 |
-
|
361 |
-
|
362 |
-
def _tree_entry_to_baseindexentry(tree_entry: "TreeCacheTup", stage: int) -> BaseIndexEntry:
|
363 |
-
return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2]))
|
364 |
-
|
365 |
-
|
366 |
-
def aggressive_tree_merge(odb: "GitCmdObjectDB", tree_shas: Sequence[bytes]) -> List[BaseIndexEntry]:
|
367 |
-
R"""
|
368 |
-
:return:
|
369 |
-
List of :class:`~git.index.typ.BaseIndexEntry`\s representing the aggressive
|
370 |
-
merge of the given trees. All valid entries are on stage 0, whereas the
|
371 |
-
conflicting ones are left on stage 1, 2 or 3, whereas stage 1 corresponds to the
|
372 |
-
common ancestor tree, 2 to our tree and 3 to 'their' tree.
|
373 |
-
|
374 |
-
:param tree_shas:
|
375 |
-
1, 2 or 3 trees as identified by their binary 20 byte shas. If 1 or two, the
|
376 |
-
entries will effectively correspond to the last given tree. If 3 are given, a 3
|
377 |
-
way merge is performed.
|
378 |
-
"""
|
379 |
-
out: List[BaseIndexEntry] = []
|
380 |
-
|
381 |
-
# One and two way is the same for us, as we don't have to handle an existing
|
382 |
-
# index, instrea
|
383 |
-
if len(tree_shas) in (1, 2):
|
384 |
-
for entry in traverse_tree_recursive(odb, tree_shas[-1], ""):
|
385 |
-
out.append(_tree_entry_to_baseindexentry(entry, 0))
|
386 |
-
# END for each entry
|
387 |
-
return out
|
388 |
-
# END handle single tree
|
389 |
-
|
390 |
-
if len(tree_shas) > 3:
|
391 |
-
raise ValueError("Cannot handle %i trees at once" % len(tree_shas))
|
392 |
-
|
393 |
-
# Three trees.
|
394 |
-
for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ""):
|
395 |
-
if base is not None:
|
396 |
-
# Base version exists.
|
397 |
-
if ours is not None:
|
398 |
-
# Ours exists.
|
399 |
-
if theirs is not None:
|
400 |
-
# It exists in all branches. Ff it was changed in both
|
401 |
-
# its a conflict. Otherwise, we take the changed version.
|
402 |
-
# This should be the most common branch, so it comes first.
|
403 |
-
if (base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0]) or (
|
404 |
-
base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1]
|
405 |
-
):
|
406 |
-
# Changed by both.
|
407 |
-
out.append(_tree_entry_to_baseindexentry(base, 1))
|
408 |
-
out.append(_tree_entry_to_baseindexentry(ours, 2))
|
409 |
-
out.append(_tree_entry_to_baseindexentry(theirs, 3))
|
410 |
-
elif base[0] != ours[0] or base[1] != ours[1]:
|
411 |
-
# Only we changed it.
|
412 |
-
out.append(_tree_entry_to_baseindexentry(ours, 0))
|
413 |
-
else:
|
414 |
-
# Either nobody changed it, or they did. In either
|
415 |
-
# case, use theirs.
|
416 |
-
out.append(_tree_entry_to_baseindexentry(theirs, 0))
|
417 |
-
# END handle modification
|
418 |
-
else:
|
419 |
-
if ours[0] != base[0] or ours[1] != base[1]:
|
420 |
-
# They deleted it, we changed it, conflict.
|
421 |
-
out.append(_tree_entry_to_baseindexentry(base, 1))
|
422 |
-
out.append(_tree_entry_to_baseindexentry(ours, 2))
|
423 |
-
# else:
|
424 |
-
# # We didn't change it, ignore.
|
425 |
-
# pass
|
426 |
-
# END handle our change
|
427 |
-
# END handle theirs
|
428 |
-
else:
|
429 |
-
if theirs is None:
|
430 |
-
# Deleted in both, its fine - it's out.
|
431 |
-
pass
|
432 |
-
else:
|
433 |
-
if theirs[0] != base[0] or theirs[1] != base[1]:
|
434 |
-
# Deleted in ours, changed theirs, conflict.
|
435 |
-
out.append(_tree_entry_to_baseindexentry(base, 1))
|
436 |
-
out.append(_tree_entry_to_baseindexentry(theirs, 3))
|
437 |
-
# END theirs changed
|
438 |
-
# else:
|
439 |
-
# # Theirs didn't change.
|
440 |
-
# pass
|
441 |
-
# END handle theirs
|
442 |
-
# END handle ours
|
443 |
-
else:
|
444 |
-
# All three can't be None.
|
445 |
-
if ours is None:
|
446 |
-
# Added in their branch.
|
447 |
-
assert theirs is not None
|
448 |
-
out.append(_tree_entry_to_baseindexentry(theirs, 0))
|
449 |
-
elif theirs is None:
|
450 |
-
# Added in our branch.
|
451 |
-
out.append(_tree_entry_to_baseindexentry(ours, 0))
|
452 |
-
else:
|
453 |
-
# Both have it, except for the base, see whether it changed.
|
454 |
-
if ours[0] != theirs[0] or ours[1] != theirs[1]:
|
455 |
-
out.append(_tree_entry_to_baseindexentry(ours, 2))
|
456 |
-
out.append(_tree_entry_to_baseindexentry(theirs, 3))
|
457 |
-
else:
|
458 |
-
# It was added the same in both.
|
459 |
-
out.append(_tree_entry_to_baseindexentry(ours, 0))
|
460 |
-
# END handle two items
|
461 |
-
# END handle heads
|
462 |
-
# END handle base exists
|
463 |
-
# END for each entries tuple
|
464 |
-
|
465 |
-
return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/index/typ.py
DELETED
@@ -1,202 +0,0 @@
|
|
1 |
-
# This module is part of GitPython and is released under the
|
2 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
3 |
-
|
4 |
-
"""Additional types used by the index."""
|
5 |
-
|
6 |
-
__all__ = ["BlobFilter", "BaseIndexEntry", "IndexEntry", "StageType"]
|
7 |
-
|
8 |
-
from binascii import b2a_hex
|
9 |
-
from pathlib import Path
|
10 |
-
|
11 |
-
from git.objects import Blob
|
12 |
-
|
13 |
-
from .util import pack, unpack
|
14 |
-
|
15 |
-
# typing ----------------------------------------------------------------------
|
16 |
-
|
17 |
-
from typing import NamedTuple, Sequence, TYPE_CHECKING, Tuple, Union, cast
|
18 |
-
|
19 |
-
from git.types import PathLike
|
20 |
-
|
21 |
-
if TYPE_CHECKING:
|
22 |
-
from git.repo import Repo
|
23 |
-
|
24 |
-
StageType = int
|
25 |
-
|
26 |
-
# ---------------------------------------------------------------------------------
|
27 |
-
|
28 |
-
# { Invariants
|
29 |
-
CE_NAMEMASK = 0x0FFF
|
30 |
-
CE_STAGEMASK = 0x3000
|
31 |
-
CE_EXTENDED = 0x4000
|
32 |
-
CE_VALID = 0x8000
|
33 |
-
CE_STAGESHIFT = 12
|
34 |
-
|
35 |
-
# } END invariants
|
36 |
-
|
37 |
-
|
38 |
-
class BlobFilter:
|
39 |
-
"""Predicate to be used by
|
40 |
-
:meth:`IndexFile.iter_blobs <git.index.base.IndexFile.iter_blobs>` allowing to
|
41 |
-
filter only return blobs which match the given list of directories or files.
|
42 |
-
|
43 |
-
The given paths are given relative to the repository.
|
44 |
-
"""
|
45 |
-
|
46 |
-
__slots__ = ("paths",)
|
47 |
-
|
48 |
-
def __init__(self, paths: Sequence[PathLike]) -> None:
|
49 |
-
"""
|
50 |
-
:param paths:
|
51 |
-
Tuple or list of paths which are either pointing to directories or to files
|
52 |
-
relative to the current repository.
|
53 |
-
"""
|
54 |
-
self.paths = paths
|
55 |
-
|
56 |
-
def __call__(self, stage_blob: Tuple[StageType, Blob]) -> bool:
|
57 |
-
blob_pathlike: PathLike = stage_blob[1].path
|
58 |
-
blob_path: Path = blob_pathlike if isinstance(blob_pathlike, Path) else Path(blob_pathlike)
|
59 |
-
for pathlike in self.paths:
|
60 |
-
path: Path = pathlike if isinstance(pathlike, Path) else Path(pathlike)
|
61 |
-
# TODO: Change to use `PosixPath.is_relative_to` once Python 3.8 is no
|
62 |
-
# longer supported.
|
63 |
-
filter_parts = path.parts
|
64 |
-
blob_parts = blob_path.parts
|
65 |
-
if len(filter_parts) > len(blob_parts):
|
66 |
-
continue
|
67 |
-
if all(i == j for i, j in zip(filter_parts, blob_parts)):
|
68 |
-
return True
|
69 |
-
return False
|
70 |
-
|
71 |
-
|
72 |
-
class BaseIndexEntryHelper(NamedTuple):
|
73 |
-
"""Typed named tuple to provide named attribute access for :class:`BaseIndexEntry`.
|
74 |
-
|
75 |
-
This is needed to allow overriding ``__new__`` in child class to preserve backwards
|
76 |
-
compatibility.
|
77 |
-
"""
|
78 |
-
|
79 |
-
mode: int
|
80 |
-
binsha: bytes
|
81 |
-
flags: int
|
82 |
-
path: PathLike
|
83 |
-
ctime_bytes: bytes = pack(">LL", 0, 0)
|
84 |
-
mtime_bytes: bytes = pack(">LL", 0, 0)
|
85 |
-
dev: int = 0
|
86 |
-
inode: int = 0
|
87 |
-
uid: int = 0
|
88 |
-
gid: int = 0
|
89 |
-
size: int = 0
|
90 |
-
|
91 |
-
|
92 |
-
class BaseIndexEntry(BaseIndexEntryHelper):
|
93 |
-
R"""Small brother of an index entry which can be created to describe changes
|
94 |
-
done to the index in which case plenty of additional information is not required.
|
95 |
-
|
96 |
-
As the first 4 data members match exactly to the :class:`IndexEntry` type, methods
|
97 |
-
expecting a :class:`BaseIndexEntry` can also handle full :class:`IndexEntry`\s even
|
98 |
-
if they use numeric indices for performance reasons.
|
99 |
-
"""
|
100 |
-
|
101 |
-
def __new__(
|
102 |
-
cls,
|
103 |
-
inp_tuple: Union[
|
104 |
-
Tuple[int, bytes, int, PathLike],
|
105 |
-
Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int],
|
106 |
-
],
|
107 |
-
) -> "BaseIndexEntry":
|
108 |
-
"""Override ``__new__`` to allow construction from a tuple for backwards
|
109 |
-
compatibility."""
|
110 |
-
return super().__new__(cls, *inp_tuple)
|
111 |
-
|
112 |
-
def __str__(self) -> str:
|
113 |
-
return "%o %s %i\t%s" % (self.mode, self.hexsha, self.stage, self.path)
|
114 |
-
|
115 |
-
def __repr__(self) -> str:
|
116 |
-
return "(%o, %s, %i, %s)" % (self.mode, self.hexsha, self.stage, self.path)
|
117 |
-
|
118 |
-
@property
|
119 |
-
def hexsha(self) -> str:
|
120 |
-
"""hex version of our sha"""
|
121 |
-
return b2a_hex(self.binsha).decode("ascii")
|
122 |
-
|
123 |
-
@property
|
124 |
-
def stage(self) -> int:
|
125 |
-
"""Stage of the entry, either:
|
126 |
-
|
127 |
-
* 0 = default stage
|
128 |
-
* 1 = stage before a merge or common ancestor entry in case of a 3 way merge
|
129 |
-
* 2 = stage of entries from the 'left' side of the merge
|
130 |
-
* 3 = stage of entries from the 'right' side of the merge
|
131 |
-
|
132 |
-
:note:
|
133 |
-
For more information, see :manpage:`git-read-tree(1)`.
|
134 |
-
"""
|
135 |
-
return (self.flags & CE_STAGEMASK) >> CE_STAGESHIFT
|
136 |
-
|
137 |
-
@classmethod
|
138 |
-
def from_blob(cls, blob: Blob, stage: int = 0) -> "BaseIndexEntry":
|
139 |
-
""":return: Fully equipped BaseIndexEntry at the given stage"""
|
140 |
-
return cls((blob.mode, blob.binsha, stage << CE_STAGESHIFT, blob.path))
|
141 |
-
|
142 |
-
def to_blob(self, repo: "Repo") -> Blob:
|
143 |
-
""":return: Blob using the information of this index entry"""
|
144 |
-
return Blob(repo, self.binsha, self.mode, self.path)
|
145 |
-
|
146 |
-
|
147 |
-
class IndexEntry(BaseIndexEntry):
|
148 |
-
"""Allows convenient access to index entry data as defined in
|
149 |
-
:class:`BaseIndexEntry` without completely unpacking it.
|
150 |
-
|
151 |
-
Attributes usually accessed often are cached in the tuple whereas others are
|
152 |
-
unpacked on demand.
|
153 |
-
|
154 |
-
See the properties for a mapping between names and tuple indices.
|
155 |
-
"""
|
156 |
-
|
157 |
-
@property
|
158 |
-
def ctime(self) -> Tuple[int, int]:
|
159 |
-
"""
|
160 |
-
:return:
|
161 |
-
Tuple(int_time_seconds_since_epoch, int_nano_seconds) of the
|
162 |
-
file's creation time
|
163 |
-
"""
|
164 |
-
return cast(Tuple[int, int], unpack(">LL", self.ctime_bytes))
|
165 |
-
|
166 |
-
@property
|
167 |
-
def mtime(self) -> Tuple[int, int]:
|
168 |
-
"""See :attr:`ctime` property, but returns modification time."""
|
169 |
-
return cast(Tuple[int, int], unpack(">LL", self.mtime_bytes))
|
170 |
-
|
171 |
-
@classmethod
|
172 |
-
def from_base(cls, base: "BaseIndexEntry") -> "IndexEntry":
|
173 |
-
"""
|
174 |
-
:return:
|
175 |
-
Minimal entry as created from the given :class:`BaseIndexEntry` instance.
|
176 |
-
Missing values will be set to null-like values.
|
177 |
-
|
178 |
-
:param base:
|
179 |
-
Instance of type :class:`BaseIndexEntry`.
|
180 |
-
"""
|
181 |
-
time = pack(">LL", 0, 0)
|
182 |
-
return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0))
|
183 |
-
|
184 |
-
@classmethod
|
185 |
-
def from_blob(cls, blob: Blob, stage: int = 0) -> "IndexEntry":
|
186 |
-
""":return: Minimal entry resembling the given blob object"""
|
187 |
-
time = pack(">LL", 0, 0)
|
188 |
-
return IndexEntry(
|
189 |
-
(
|
190 |
-
blob.mode,
|
191 |
-
blob.binsha,
|
192 |
-
stage << CE_STAGESHIFT,
|
193 |
-
blob.path,
|
194 |
-
time,
|
195 |
-
time,
|
196 |
-
0,
|
197 |
-
0,
|
198 |
-
0,
|
199 |
-
0,
|
200 |
-
blob.size,
|
201 |
-
)
|
202 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/index/util.py
DELETED
@@ -1,121 +0,0 @@
|
|
1 |
-
# This module is part of GitPython and is released under the
|
2 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
3 |
-
|
4 |
-
"""Index utilities."""
|
5 |
-
|
6 |
-
__all__ = ["TemporaryFileSwap", "post_clear_cache", "default_index", "git_working_dir"]
|
7 |
-
|
8 |
-
import contextlib
|
9 |
-
from functools import wraps
|
10 |
-
import os
|
11 |
-
import os.path as osp
|
12 |
-
import struct
|
13 |
-
import tempfile
|
14 |
-
from types import TracebackType
|
15 |
-
|
16 |
-
# typing ----------------------------------------------------------------------
|
17 |
-
|
18 |
-
from typing import Any, Callable, TYPE_CHECKING, Optional, Type
|
19 |
-
|
20 |
-
from git.types import Literal, PathLike, _T
|
21 |
-
|
22 |
-
if TYPE_CHECKING:
|
23 |
-
from git.index import IndexFile
|
24 |
-
|
25 |
-
# ---------------------------------------------------------------------------------
|
26 |
-
|
27 |
-
# { Aliases
|
28 |
-
pack = struct.pack
|
29 |
-
unpack = struct.unpack
|
30 |
-
# } END aliases
|
31 |
-
|
32 |
-
|
33 |
-
class TemporaryFileSwap:
|
34 |
-
"""Utility class moving a file to a temporary location within the same directory and
|
35 |
-
moving it back on to where on object deletion."""
|
36 |
-
|
37 |
-
__slots__ = ("file_path", "tmp_file_path")
|
38 |
-
|
39 |
-
def __init__(self, file_path: PathLike) -> None:
|
40 |
-
self.file_path = file_path
|
41 |
-
dirname, basename = osp.split(file_path)
|
42 |
-
fd, self.tmp_file_path = tempfile.mkstemp(prefix=basename, dir=dirname)
|
43 |
-
os.close(fd)
|
44 |
-
with contextlib.suppress(OSError): # It may be that the source does not exist.
|
45 |
-
os.replace(self.file_path, self.tmp_file_path)
|
46 |
-
|
47 |
-
def __enter__(self) -> "TemporaryFileSwap":
|
48 |
-
return self
|
49 |
-
|
50 |
-
def __exit__(
|
51 |
-
self,
|
52 |
-
exc_type: Optional[Type[BaseException]],
|
53 |
-
exc_val: Optional[BaseException],
|
54 |
-
exc_tb: Optional[TracebackType],
|
55 |
-
) -> Literal[False]:
|
56 |
-
if osp.isfile(self.tmp_file_path):
|
57 |
-
os.replace(self.tmp_file_path, self.file_path)
|
58 |
-
return False
|
59 |
-
|
60 |
-
|
61 |
-
# { Decorators
|
62 |
-
|
63 |
-
|
64 |
-
def post_clear_cache(func: Callable[..., _T]) -> Callable[..., _T]:
|
65 |
-
"""Decorator for functions that alter the index using the git command.
|
66 |
-
|
67 |
-
When a git command alters the index, this invalidates our possibly existing entries
|
68 |
-
dictionary, which is why it must be deleted to allow it to be lazily reread later.
|
69 |
-
"""
|
70 |
-
|
71 |
-
@wraps(func)
|
72 |
-
def post_clear_cache_if_not_raised(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
|
73 |
-
rval = func(self, *args, **kwargs)
|
74 |
-
self._delete_entries_cache()
|
75 |
-
return rval
|
76 |
-
|
77 |
-
# END wrapper method
|
78 |
-
|
79 |
-
return post_clear_cache_if_not_raised
|
80 |
-
|
81 |
-
|
82 |
-
def default_index(func: Callable[..., _T]) -> Callable[..., _T]:
|
83 |
-
"""Decorator ensuring the wrapped method may only run if we are the default
|
84 |
-
repository index.
|
85 |
-
|
86 |
-
This is as we rely on git commands that operate on that index only.
|
87 |
-
"""
|
88 |
-
|
89 |
-
@wraps(func)
|
90 |
-
def check_default_index(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
|
91 |
-
if self._file_path != self._index_path():
|
92 |
-
raise AssertionError(
|
93 |
-
"Cannot call %r on indices that do not represent the default git index" % func.__name__
|
94 |
-
)
|
95 |
-
return func(self, *args, **kwargs)
|
96 |
-
|
97 |
-
# END wrapper method
|
98 |
-
|
99 |
-
return check_default_index
|
100 |
-
|
101 |
-
|
102 |
-
def git_working_dir(func: Callable[..., _T]) -> Callable[..., _T]:
|
103 |
-
"""Decorator which changes the current working dir to the one of the git
|
104 |
-
repository in order to ensure relative paths are handled correctly."""
|
105 |
-
|
106 |
-
@wraps(func)
|
107 |
-
def set_git_working_dir(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
|
108 |
-
cur_wd = os.getcwd()
|
109 |
-
os.chdir(str(self.repo.working_tree_dir))
|
110 |
-
try:
|
111 |
-
return func(self, *args, **kwargs)
|
112 |
-
finally:
|
113 |
-
os.chdir(cur_wd)
|
114 |
-
# END handle working dir
|
115 |
-
|
116 |
-
# END wrapper
|
117 |
-
|
118 |
-
return set_git_working_dir
|
119 |
-
|
120 |
-
|
121 |
-
# } END decorators
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/objects/__init__.py
DELETED
@@ -1,25 +0,0 @@
|
|
1 |
-
# This module is part of GitPython and is released under the
|
2 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
3 |
-
|
4 |
-
"""Import all submodules' main classes into the package space."""
|
5 |
-
|
6 |
-
__all__ = [
|
7 |
-
"IndexObject",
|
8 |
-
"Object",
|
9 |
-
"Blob",
|
10 |
-
"Commit",
|
11 |
-
"Submodule",
|
12 |
-
"UpdateProgress",
|
13 |
-
"RootModule",
|
14 |
-
"RootUpdateProgress",
|
15 |
-
"TagObject",
|
16 |
-
"Tree",
|
17 |
-
"TreeModifier",
|
18 |
-
]
|
19 |
-
|
20 |
-
from .base import IndexObject, Object
|
21 |
-
from .blob import Blob
|
22 |
-
from .commit import Commit
|
23 |
-
from .submodule import RootModule, RootUpdateProgress, Submodule, UpdateProgress
|
24 |
-
from .tag import TagObject
|
25 |
-
from .tree import Tree, TreeModifier
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/objects/__pycache__/__init__.cpython-311.pyc
DELETED
Binary file (873 Bytes)
|
|
ILYA/Lib/site-packages/git/objects/__pycache__/base.cpython-311.pyc
DELETED
Binary file (13.3 kB)
|
|
ILYA/Lib/site-packages/git/objects/__pycache__/blob.cpython-311.pyc
DELETED
Binary file (1.85 kB)
|
|
ILYA/Lib/site-packages/git/objects/__pycache__/commit.cpython-311.pyc
DELETED
Binary file (35.1 kB)
|
|
ILYA/Lib/site-packages/git/objects/__pycache__/fun.cpython-311.pyc
DELETED
Binary file (9.5 kB)
|
|
ILYA/Lib/site-packages/git/objects/__pycache__/tag.cpython-311.pyc
DELETED
Binary file (5.49 kB)
|
|
ILYA/Lib/site-packages/git/objects/__pycache__/tree.cpython-311.pyc
DELETED
Binary file (19.1 kB)
|
|
ILYA/Lib/site-packages/git/objects/__pycache__/util.cpython-311.pyc
DELETED
Binary file (30.4 kB)
|
|
ILYA/Lib/site-packages/git/objects/base.py
DELETED
@@ -1,301 +0,0 @@
|
|
1 |
-
# Copyright (C) 2008, 2009 Michael Trier ([email protected]) and contributors
|
2 |
-
#
|
3 |
-
# This module is part of GitPython and is released under the
|
4 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
5 |
-
|
6 |
-
__all__ = ["Object", "IndexObject"]
|
7 |
-
|
8 |
-
import os.path as osp
|
9 |
-
|
10 |
-
import gitdb.typ as dbtyp
|
11 |
-
|
12 |
-
from git.exc import WorkTreeRepositoryUnsupported
|
13 |
-
from git.util import LazyMixin, bin_to_hex, join_path_native, stream_copy
|
14 |
-
|
15 |
-
from .util import get_object_type_by_name
|
16 |
-
|
17 |
-
# typing ------------------------------------------------------------------
|
18 |
-
|
19 |
-
from typing import Any, TYPE_CHECKING, Union
|
20 |
-
|
21 |
-
from git.types import AnyGitObject, GitObjectTypeString, PathLike
|
22 |
-
|
23 |
-
if TYPE_CHECKING:
|
24 |
-
from gitdb.base import OStream
|
25 |
-
|
26 |
-
from git.refs.reference import Reference
|
27 |
-
from git.repo import Repo
|
28 |
-
|
29 |
-
from .blob import Blob
|
30 |
-
from .submodule.base import Submodule
|
31 |
-
from .tree import Tree
|
32 |
-
|
33 |
-
IndexObjUnion = Union["Tree", "Blob", "Submodule"]
|
34 |
-
|
35 |
-
# --------------------------------------------------------------------------
|
36 |
-
|
37 |
-
|
38 |
-
class Object(LazyMixin):
|
39 |
-
"""Base class for classes representing git object types.
|
40 |
-
|
41 |
-
The following four leaf classes represent specific kinds of git objects:
|
42 |
-
|
43 |
-
* :class:`Blob <git.objects.blob.Blob>`
|
44 |
-
* :class:`Tree <git.objects.tree.Tree>`
|
45 |
-
* :class:`Commit <git.objects.commit.Commit>`
|
46 |
-
* :class:`TagObject <git.objects.tag.TagObject>`
|
47 |
-
|
48 |
-
See :manpage:`gitglossary(7)` on:
|
49 |
-
|
50 |
-
* "object": https://git-scm.com/docs/gitglossary#def_object
|
51 |
-
* "object type": https://git-scm.com/docs/gitglossary#def_object_type
|
52 |
-
* "blob": https://git-scm.com/docs/gitglossary#def_blob_object
|
53 |
-
* "tree object": https://git-scm.com/docs/gitglossary#def_tree_object
|
54 |
-
* "commit object": https://git-scm.com/docs/gitglossary#def_commit_object
|
55 |
-
* "tag object": https://git-scm.com/docs/gitglossary#def_tag_object
|
56 |
-
|
57 |
-
:note:
|
58 |
-
See the :class:`~git.types.AnyGitObject` union type of the four leaf subclasses
|
59 |
-
that represent actual git object types.
|
60 |
-
|
61 |
-
:note:
|
62 |
-
:class:`~git.objects.submodule.base.Submodule` is defined under the hierarchy
|
63 |
-
rooted at this :class:`Object` class, even though submodules are not really a
|
64 |
-
type of git object. (This also applies to its
|
65 |
-
:class:`~git.objects.submodule.root.RootModule` subclass.)
|
66 |
-
|
67 |
-
:note:
|
68 |
-
This :class:`Object` class should not be confused with :class:`object` (the root
|
69 |
-
of the class hierarchy in Python).
|
70 |
-
"""
|
71 |
-
|
72 |
-
NULL_HEX_SHA = "0" * 40
|
73 |
-
NULL_BIN_SHA = b"\0" * 20
|
74 |
-
|
75 |
-
TYPES = (
|
76 |
-
dbtyp.str_blob_type,
|
77 |
-
dbtyp.str_tree_type,
|
78 |
-
dbtyp.str_commit_type,
|
79 |
-
dbtyp.str_tag_type,
|
80 |
-
)
|
81 |
-
|
82 |
-
__slots__ = ("repo", "binsha", "size")
|
83 |
-
|
84 |
-
type: Union[GitObjectTypeString, None] = None
|
85 |
-
"""String identifying (a concrete :class:`Object` subtype for) a git object type.
|
86 |
-
|
87 |
-
The subtypes that this may name correspond to the kinds of git objects that exist,
|
88 |
-
i.e., the objects that may be present in a git repository.
|
89 |
-
|
90 |
-
:note:
|
91 |
-
Most subclasses represent specific types of git objects and override this class
|
92 |
-
attribute accordingly. This attribute is ``None`` in the :class:`Object` base
|
93 |
-
class, as well as the :class:`IndexObject` intermediate subclass, but never
|
94 |
-
``None`` in concrete leaf subclasses representing specific git object types.
|
95 |
-
|
96 |
-
:note:
|
97 |
-
See also :class:`~git.types.GitObjectTypeString`.
|
98 |
-
"""
|
99 |
-
|
100 |
-
def __init__(self, repo: "Repo", binsha: bytes) -> None:
|
101 |
-
"""Initialize an object by identifying it by its binary sha.
|
102 |
-
|
103 |
-
All keyword arguments will be set on demand if ``None``.
|
104 |
-
|
105 |
-
:param repo:
|
106 |
-
Repository this object is located in.
|
107 |
-
|
108 |
-
:param binsha:
|
109 |
-
20 byte SHA1
|
110 |
-
"""
|
111 |
-
super().__init__()
|
112 |
-
self.repo = repo
|
113 |
-
self.binsha = binsha
|
114 |
-
assert len(binsha) == 20, "Require 20 byte binary sha, got %r, len = %i" % (
|
115 |
-
binsha,
|
116 |
-
len(binsha),
|
117 |
-
)
|
118 |
-
|
119 |
-
@classmethod
|
120 |
-
def new(cls, repo: "Repo", id: Union[str, "Reference"]) -> AnyGitObject:
|
121 |
-
"""
|
122 |
-
:return:
|
123 |
-
New :class:`Object` instance of a type appropriate to the object type behind
|
124 |
-
`id`. The id of the newly created object will be a binsha even though the
|
125 |
-
input id may have been a `~git.refs.reference.Reference` or rev-spec.
|
126 |
-
|
127 |
-
:param id:
|
128 |
-
:class:`~git.refs.reference.Reference`, rev-spec, or hexsha.
|
129 |
-
|
130 |
-
:note:
|
131 |
-
This cannot be a ``__new__`` method as it would always call :meth:`__init__`
|
132 |
-
with the input id which is not necessarily a binsha.
|
133 |
-
"""
|
134 |
-
return repo.rev_parse(str(id))
|
135 |
-
|
136 |
-
@classmethod
|
137 |
-
def new_from_sha(cls, repo: "Repo", sha1: bytes) -> AnyGitObject:
|
138 |
-
"""
|
139 |
-
:return:
|
140 |
-
New object instance of a type appropriate to represent the given binary sha1
|
141 |
-
|
142 |
-
:param sha1:
|
143 |
-
20 byte binary sha1.
|
144 |
-
"""
|
145 |
-
if sha1 == cls.NULL_BIN_SHA:
|
146 |
-
# The NULL binsha is always the root commit.
|
147 |
-
return get_object_type_by_name(b"commit")(repo, sha1)
|
148 |
-
# END handle special case
|
149 |
-
oinfo = repo.odb.info(sha1)
|
150 |
-
inst = get_object_type_by_name(oinfo.type)(repo, oinfo.binsha)
|
151 |
-
inst.size = oinfo.size
|
152 |
-
return inst
|
153 |
-
|
154 |
-
def _set_cache_(self, attr: str) -> None:
|
155 |
-
"""Retrieve object information."""
|
156 |
-
if attr == "size":
|
157 |
-
oinfo = self.repo.odb.info(self.binsha)
|
158 |
-
self.size = oinfo.size # type: int
|
159 |
-
else:
|
160 |
-
super()._set_cache_(attr)
|
161 |
-
|
162 |
-
def __eq__(self, other: Any) -> bool:
|
163 |
-
""":return: ``True`` if the objects have the same SHA1"""
|
164 |
-
if not hasattr(other, "binsha"):
|
165 |
-
return False
|
166 |
-
return self.binsha == other.binsha
|
167 |
-
|
168 |
-
def __ne__(self, other: Any) -> bool:
|
169 |
-
""":return: ``True`` if the objects do not have the same SHA1"""
|
170 |
-
if not hasattr(other, "binsha"):
|
171 |
-
return True
|
172 |
-
return self.binsha != other.binsha
|
173 |
-
|
174 |
-
def __hash__(self) -> int:
|
175 |
-
""":return: Hash of our id allowing objects to be used in dicts and sets"""
|
176 |
-
return hash(self.binsha)
|
177 |
-
|
178 |
-
def __str__(self) -> str:
|
179 |
-
""":return: String of our SHA1 as understood by all git commands"""
|
180 |
-
return self.hexsha
|
181 |
-
|
182 |
-
def __repr__(self) -> str:
|
183 |
-
""":return: String with pythonic representation of our object"""
|
184 |
-
return '<git.%s "%s">' % (self.__class__.__name__, self.hexsha)
|
185 |
-
|
186 |
-
@property
|
187 |
-
def hexsha(self) -> str:
|
188 |
-
""":return: 40 byte hex version of our 20 byte binary sha"""
|
189 |
-
# b2a_hex produces bytes.
|
190 |
-
return bin_to_hex(self.binsha).decode("ascii")
|
191 |
-
|
192 |
-
@property
|
193 |
-
def data_stream(self) -> "OStream":
|
194 |
-
"""
|
195 |
-
:return:
|
196 |
-
File-object compatible stream to the uncompressed raw data of the object
|
197 |
-
|
198 |
-
:note:
|
199 |
-
Returned streams must be read in order.
|
200 |
-
"""
|
201 |
-
return self.repo.odb.stream(self.binsha)
|
202 |
-
|
203 |
-
def stream_data(self, ostream: "OStream") -> "Object":
|
204 |
-
"""Write our data directly to the given output stream.
|
205 |
-
|
206 |
-
:param ostream:
|
207 |
-
File-object compatible stream object.
|
208 |
-
|
209 |
-
:return:
|
210 |
-
self
|
211 |
-
"""
|
212 |
-
istream = self.repo.odb.stream(self.binsha)
|
213 |
-
stream_copy(istream, ostream)
|
214 |
-
return self
|
215 |
-
|
216 |
-
|
217 |
-
class IndexObject(Object):
|
218 |
-
"""Base for all objects that can be part of the index file.
|
219 |
-
|
220 |
-
The classes representing git object types that can be part of the index file are
|
221 |
-
:class:`~git.objects.tree.Tree and :class:`~git.objects.blob.Blob`. In addition,
|
222 |
-
:class:`~git.objects.submodule.base.Submodule`, which is not really a git object
|
223 |
-
type but can be part of an index file, is also a subclass.
|
224 |
-
"""
|
225 |
-
|
226 |
-
__slots__ = ("path", "mode")
|
227 |
-
|
228 |
-
# For compatibility with iterable lists.
|
229 |
-
_id_attribute_ = "path"
|
230 |
-
|
231 |
-
def __init__(
|
232 |
-
self,
|
233 |
-
repo: "Repo",
|
234 |
-
binsha: bytes,
|
235 |
-
mode: Union[None, int] = None,
|
236 |
-
path: Union[None, PathLike] = None,
|
237 |
-
) -> None:
|
238 |
-
"""Initialize a newly instanced :class:`IndexObject`.
|
239 |
-
|
240 |
-
:param repo:
|
241 |
-
The :class:`~git.repo.base.Repo` we are located in.
|
242 |
-
|
243 |
-
:param binsha:
|
244 |
-
20 byte sha1.
|
245 |
-
|
246 |
-
:param mode:
|
247 |
-
The stat-compatible file mode as :class:`int`.
|
248 |
-
Use the :mod:`stat` module to evaluate the information.
|
249 |
-
|
250 |
-
:param path:
|
251 |
-
The path to the file in the file system, relative to the git repository
|
252 |
-
root, like ``file.ext`` or ``folder/other.ext``.
|
253 |
-
|
254 |
-
:note:
|
255 |
-
Path may not be set if the index object has been created directly, as it
|
256 |
-
cannot be retrieved without knowing the parent tree.
|
257 |
-
"""
|
258 |
-
super().__init__(repo, binsha)
|
259 |
-
if mode is not None:
|
260 |
-
self.mode = mode
|
261 |
-
if path is not None:
|
262 |
-
self.path = path
|
263 |
-
|
264 |
-
def __hash__(self) -> int:
|
265 |
-
"""
|
266 |
-
:return:
|
267 |
-
Hash of our path as index items are uniquely identifiable by path, not by
|
268 |
-
their data!
|
269 |
-
"""
|
270 |
-
return hash(self.path)
|
271 |
-
|
272 |
-
def _set_cache_(self, attr: str) -> None:
|
273 |
-
if attr in IndexObject.__slots__:
|
274 |
-
# They cannot be retrieved later on (not without searching for them).
|
275 |
-
raise AttributeError(
|
276 |
-
"Attribute '%s' unset: path and mode attributes must have been set during %s object creation"
|
277 |
-
% (attr, type(self).__name__)
|
278 |
-
)
|
279 |
-
else:
|
280 |
-
super()._set_cache_(attr)
|
281 |
-
# END handle slot attribute
|
282 |
-
|
283 |
-
@property
|
284 |
-
def name(self) -> str:
|
285 |
-
""":return: Name portion of the path, effectively being the basename"""
|
286 |
-
return osp.basename(self.path)
|
287 |
-
|
288 |
-
@property
|
289 |
-
def abspath(self) -> PathLike:
|
290 |
-
R"""
|
291 |
-
:return:
|
292 |
-
Absolute path to this index object in the file system (as opposed to the
|
293 |
-
:attr:`path` field which is a path relative to the git repository).
|
294 |
-
|
295 |
-
The returned path will be native to the system and contains ``\`` on
|
296 |
-
Windows.
|
297 |
-
"""
|
298 |
-
if self.repo.working_tree_dir is not None:
|
299 |
-
return join_path_native(self.repo.working_tree_dir, self.path)
|
300 |
-
else:
|
301 |
-
raise WorkTreeRepositoryUnsupported("working_tree_dir was None or empty")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/objects/blob.py
DELETED
@@ -1,48 +0,0 @@
|
|
1 |
-
# Copyright (C) 2008, 2009 Michael Trier ([email protected]) and contributors
|
2 |
-
#
|
3 |
-
# This module is part of GitPython and is released under the
|
4 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
5 |
-
|
6 |
-
__all__ = ["Blob"]
|
7 |
-
|
8 |
-
from mimetypes import guess_type
|
9 |
-
import sys
|
10 |
-
|
11 |
-
if sys.version_info >= (3, 8):
|
12 |
-
from typing import Literal
|
13 |
-
else:
|
14 |
-
from typing_extensions import Literal
|
15 |
-
|
16 |
-
from . import base
|
17 |
-
|
18 |
-
|
19 |
-
class Blob(base.IndexObject):
|
20 |
-
"""A Blob encapsulates a git blob object.
|
21 |
-
|
22 |
-
See :manpage:`gitglossary(7)` on "blob":
|
23 |
-
https://git-scm.com/docs/gitglossary#def_blob_object
|
24 |
-
"""
|
25 |
-
|
26 |
-
DEFAULT_MIME_TYPE = "text/plain"
|
27 |
-
type: Literal["blob"] = "blob"
|
28 |
-
|
29 |
-
# Valid blob modes
|
30 |
-
executable_mode = 0o100755
|
31 |
-
file_mode = 0o100644
|
32 |
-
link_mode = 0o120000
|
33 |
-
|
34 |
-
__slots__ = ()
|
35 |
-
|
36 |
-
@property
|
37 |
-
def mime_type(self) -> str:
|
38 |
-
"""
|
39 |
-
:return:
|
40 |
-
String describing the mime type of this file (based on the filename)
|
41 |
-
|
42 |
-
:note:
|
43 |
-
Defaults to ``text/plain`` in case the actual file type is unknown.
|
44 |
-
"""
|
45 |
-
guesses = None
|
46 |
-
if self.path:
|
47 |
-
guesses = guess_type(str(self.path))
|
48 |
-
return guesses and guesses[0] or self.DEFAULT_MIME_TYPE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ILYA/Lib/site-packages/git/objects/commit.py
DELETED
@@ -1,899 +0,0 @@
|
|
1 |
-
# Copyright (C) 2008, 2009 Michael Trier ([email protected]) and contributors
|
2 |
-
#
|
3 |
-
# This module is part of GitPython and is released under the
|
4 |
-
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
|
5 |
-
|
6 |
-
__all__ = ["Commit"]
|
7 |
-
|
8 |
-
from collections import defaultdict
|
9 |
-
import datetime
|
10 |
-
from io import BytesIO
|
11 |
-
import logging
|
12 |
-
import os
|
13 |
-
import re
|
14 |
-
from subprocess import Popen, PIPE
|
15 |
-
import sys
|
16 |
-
from time import altzone, daylight, localtime, time, timezone
|
17 |
-
import warnings
|
18 |
-
|
19 |
-
from gitdb import IStream
|
20 |
-
|
21 |
-
from git.cmd import Git
|
22 |
-
from git.diff import Diffable
|
23 |
-
from git.util import Actor, Stats, finalize_process, hex_to_bin
|
24 |
-
|
25 |
-
from . import base
|
26 |
-
from .tree import Tree
|
27 |
-
from .util import (
|
28 |
-
Serializable,
|
29 |
-
TraversableIterableObj,
|
30 |
-
altz_to_utctz_str,
|
31 |
-
from_timestamp,
|
32 |
-
parse_actor_and_date,
|
33 |
-
parse_date,
|
34 |
-
)
|
35 |
-
|
36 |
-
# typing ------------------------------------------------------------------
|
37 |
-
|
38 |
-
from typing import (
|
39 |
-
Any,
|
40 |
-
Dict,
|
41 |
-
IO,
|
42 |
-
Iterator,
|
43 |
-
List,
|
44 |
-
Sequence,
|
45 |
-
Tuple,
|
46 |
-
TYPE_CHECKING,
|
47 |
-
Union,
|
48 |
-
cast,
|
49 |
-
)
|
50 |
-
|
51 |
-
if sys.version_info >= (3, 8):
|
52 |
-
from typing import Literal
|
53 |
-
else:
|
54 |
-
from typing_extensions import Literal
|
55 |
-
|
56 |
-
from git.types import PathLike
|
57 |
-
|
58 |
-
if TYPE_CHECKING:
|
59 |
-
from git.refs import SymbolicReference
|
60 |
-
from git.repo import Repo
|
61 |
-
|
62 |
-
# ------------------------------------------------------------------------
|
63 |
-
|
64 |
-
_logger = logging.getLogger(__name__)
|
65 |
-
|
66 |
-
|
67 |
-
class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
|
68 |
-
"""Wraps a git commit object.
|
69 |
-
|
70 |
-
See :manpage:`gitglossary(7)` on "commit object":
|
71 |
-
https://git-scm.com/docs/gitglossary#def_commit_object
|
72 |
-
|
73 |
-
:note:
|
74 |
-
This class will act lazily on some of its attributes and will query the value on
|
75 |
-
demand only if it involves calling the git binary.
|
76 |
-
"""
|
77 |
-
|
78 |
-
# ENVIRONMENT VARIABLES
|
79 |
-
# Read when creating new commits.
|
80 |
-
env_author_date = "GIT_AUTHOR_DATE"
|
81 |
-
env_committer_date = "GIT_COMMITTER_DATE"
|
82 |
-
|
83 |
-
# CONFIGURATION KEYS
|
84 |
-
conf_encoding = "i18n.commitencoding"
|
85 |
-
|
86 |
-
# INVARIANTS
|
87 |
-
default_encoding = "UTF-8"
|
88 |
-
|
89 |
-
type: Literal["commit"] = "commit"
|
90 |
-
|
91 |
-
__slots__ = (
|
92 |
-
"tree",
|
93 |
-
"author",
|
94 |
-
"authored_date",
|
95 |
-
"author_tz_offset",
|
96 |
-
"committer",
|
97 |
-
"committed_date",
|
98 |
-
"committer_tz_offset",
|
99 |
-
"message",
|
100 |
-
"parents",
|
101 |
-
"encoding",
|
102 |
-
"gpgsig",
|
103 |
-
)
|
104 |
-
|
105 |
-
_id_attribute_ = "hexsha"
|
106 |
-
|
107 |
-
parents: Sequence["Commit"]
|
108 |
-
|
109 |
-
def __init__(
|
110 |
-
self,
|
111 |
-
repo: "Repo",
|
112 |
-
binsha: bytes,
|
113 |
-
tree: Union[Tree, None] = None,
|
114 |
-
author: Union[Actor, None] = None,
|
115 |
-
authored_date: Union[int, None] = None,
|
116 |
-
author_tz_offset: Union[None, float] = None,
|
117 |
-
committer: Union[Actor, None] = None,
|
118 |
-
committed_date: Union[int, None] = None,
|
119 |
-
committer_tz_offset: Union[None, float] = None,
|
120 |
-
message: Union[str, bytes, None] = None,
|
121 |
-
parents: Union[Sequence["Commit"], None] = None,
|
122 |
-
encoding: Union[str, None] = None,
|
123 |
-
gpgsig: Union[str, None] = None,
|
124 |
-
) -> None:
|
125 |
-
"""Instantiate a new :class:`Commit`. All keyword arguments taking ``None`` as
|
126 |
-
default will be implicitly set on first query.
|
127 |
-
|
128 |
-
:param binsha:
|
129 |
-
20 byte sha1.
|
130 |
-
|
131 |
-
:param tree:
|
132 |
-
A :class:`~git.objects.tree.Tree` object.
|
133 |
-
|
134 |
-
:param author:
|
135 |
-
The author :class:`~git.util.Actor` object.
|
136 |
-
|
137 |
-
:param authored_date: int_seconds_since_epoch
|
138 |
-
The authored DateTime - use :func:`time.gmtime` to convert it into a
|
139 |
-
different format.
|
140 |
-
|
141 |
-
:param author_tz_offset: int_seconds_west_of_utc
|
142 |
-
The timezone that the `authored_date` is in.
|
143 |
-
|
144 |
-
:param committer:
|
145 |
-
The committer string, as an :class:`~git.util.Actor` object.
|
146 |
-
|
147 |
-
:param committed_date: int_seconds_since_epoch
|
148 |
-
The committed DateTime - use :func:`time.gmtime` to convert it into a
|
149 |
-
different format.
|
150 |
-
|
151 |
-
:param committer_tz_offset: int_seconds_west_of_utc
|
152 |
-
The timezone that the `committed_date` is in.
|
153 |
-
|
154 |
-
:param message: string
|
155 |
-
The commit message.
|
156 |
-
|
157 |
-
:param encoding: string
|
158 |
-
Encoding of the message, defaults to UTF-8.
|
159 |
-
|
160 |
-
:param parents:
|
161 |
-
List or tuple of :class:`Commit` objects which are our parent(s) in the
|
162 |
-
commit dependency graph.
|
163 |
-
|
164 |
-
:return:
|
165 |
-
:class:`Commit`
|
166 |
-
|
167 |
-
:note:
|
168 |
-
Timezone information is in the same format and in the same sign as what
|
169 |
-
:func:`time.altzone` returns. The sign is inverted compared to git's UTC
|
170 |
-
timezone.
|
171 |
-
"""
|
172 |
-
super().__init__(repo, binsha)
|
173 |
-
self.binsha = binsha
|
174 |
-
if tree is not None:
|
175 |
-
assert isinstance(tree, Tree), "Tree needs to be a Tree instance, was %s" % type(tree)
|
176 |
-
if tree is not None:
|
177 |
-
self.tree = tree
|
178 |
-
if author is not None:
|
179 |
-
self.author = author
|
180 |
-
if authored_date is not None:
|
181 |
-
self.authored_date = authored_date
|
182 |
-
if author_tz_offset is not None:
|
183 |
-
self.author_tz_offset = author_tz_offset
|
184 |
-
if committer is not None:
|
185 |
-
self.committer = committer
|
186 |
-
if committed_date is not None:
|
187 |
-
self.committed_date = committed_date
|
188 |
-
if committer_tz_offset is not None:
|
189 |
-
self.committer_tz_offset = committer_tz_offset
|
190 |
-
if message is not None:
|
191 |
-
self.message = message
|
192 |
-
if parents is not None:
|
193 |
-
self.parents = parents
|
194 |
-
if encoding is not None:
|
195 |
-
self.encoding = encoding
|
196 |
-
if gpgsig is not None:
|
197 |
-
self.gpgsig = gpgsig
|
198 |
-
|
199 |
-
@classmethod
|
200 |
-
def _get_intermediate_items(cls, commit: "Commit") -> Tuple["Commit", ...]:
|
201 |
-
return tuple(commit.parents)
|
202 |
-
|
203 |
-
@classmethod
|
204 |
-
def _calculate_sha_(cls, repo: "Repo", commit: "Commit") -> bytes:
|
205 |
-
"""Calculate the sha of a commit.
|
206 |
-
|
207 |
-
:param repo:
|
208 |
-
:class:`~git.repo.base.Repo` object the commit should be part of.
|
209 |
-
|
210 |
-
:param commit:
|
211 |
-
:class:`Commit` object for which to generate the sha.
|
212 |
-
"""
|
213 |
-
|
214 |
-
stream = BytesIO()
|
215 |
-
commit._serialize(stream)
|
216 |
-
streamlen = stream.tell()
|
217 |
-
stream.seek(0)
|
218 |
-
|
219 |
-
istream = repo.odb.store(IStream(cls.type, streamlen, stream))
|
220 |
-
return istream.binsha
|
221 |
-
|
222 |
-
def replace(self, **kwargs: Any) -> "Commit":
|
223 |
-
"""Create new commit object from an existing commit object.
|
224 |
-
|
225 |
-
Any values provided as keyword arguments will replace the corresponding
|
226 |
-
attribute in the new object.
|
227 |
-
"""
|
228 |
-
|
229 |
-
attrs = {k: getattr(self, k) for k in self.__slots__}
|
230 |
-
|
231 |
-
for attrname in kwargs:
|
232 |
-
if attrname not in self.__slots__:
|
233 |
-
raise ValueError("invalid attribute name")
|
234 |
-
|
235 |
-
attrs.update(kwargs)
|
236 |
-
new_commit = self.__class__(self.repo, self.NULL_BIN_SHA, **attrs)
|
237 |
-
new_commit.binsha = self._calculate_sha_(self.repo, new_commit)
|
238 |
-
|
239 |
-
return new_commit
|
240 |
-
|
241 |
-
def _set_cache_(self, attr: str) -> None:
|
242 |
-
if attr in Commit.__slots__:
|
243 |
-
# Read the data in a chunk, its faster - then provide a file wrapper.
|
244 |
-
_binsha, _typename, self.size, stream = self.repo.odb.stream(self.binsha)
|
245 |
-
self._deserialize(BytesIO(stream.read()))
|
246 |
-
else:
|
247 |
-
super()._set_cache_(attr)
|
248 |
-
# END handle attrs
|
249 |
-
|
250 |
-
@property
|
251 |
-
def authored_datetime(self) -> datetime.datetime:
|
252 |
-
return from_timestamp(self.authored_date, self.author_tz_offset)
|
253 |
-
|
254 |
-
@property
|
255 |
-
def committed_datetime(self) -> datetime.datetime:
|
256 |
-
return from_timestamp(self.committed_date, self.committer_tz_offset)
|
257 |
-
|
258 |
-
@property
|
259 |
-
def summary(self) -> Union[str, bytes]:
|
260 |
-
""":return: First line of the commit message"""
|
261 |
-
if isinstance(self.message, str):
|
262 |
-
return self.message.split("\n", 1)[0]
|
263 |
-
else:
|
264 |
-
return self.message.split(b"\n", 1)[0]
|
265 |
-
|
266 |
-
def count(self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any) -> int:
|
267 |
-
"""Count the number of commits reachable from this commit.
|
268 |
-
|
269 |
-
:param paths:
|
270 |
-
An optional path or a list of paths restricting the return value to commits
|
271 |
-
actually containing the paths.
|
272 |
-
|
273 |
-
:param kwargs:
|
274 |
-
Additional options to be passed to :manpage:`git-rev-list(1)`. They must not
|
275 |
-
alter the output style of the command, or parsing will yield incorrect
|
276 |
-
results.
|
277 |
-
|
278 |
-
:return:
|
279 |
-
An int defining the number of reachable commits
|
280 |
-
"""
|
281 |
-
# Yes, it makes a difference whether empty paths are given or not in our case as
|
282 |
-
# the empty paths version will ignore merge commits for some reason.
|
283 |
-
if paths:
|
284 |
-
return len(self.repo.git.rev_list(self.hexsha, "--", paths, **kwargs).splitlines())
|
285 |
-
return len(self.repo.git.rev_list(self.hexsha, **kwargs).splitlines())
|
286 |
-
|
287 |
-
@property
|
288 |
-
def name_rev(self) -> str:
|
289 |
-
"""
|
290 |
-
:return:
|
291 |
-
String describing the commits hex sha based on the closest
|
292 |
-
`~git.refs.reference.Reference`.
|
293 |
-
|
294 |
-
:note:
|
295 |
-
Mostly useful for UI purposes.
|
296 |
-
"""
|
297 |
-
return self.repo.git.name_rev(self)
|
298 |
-
|
299 |
-
@classmethod
|
300 |
-
def iter_items(
|
301 |
-
cls,
|
302 |
-
repo: "Repo",
|
303 |
-
rev: Union[str, "Commit", "SymbolicReference"],
|
304 |
-
paths: Union[PathLike, Sequence[PathLike]] = "",
|
305 |
-
**kwargs: Any,
|
306 |
-
) -> Iterator["Commit"]:
|
307 |
-
R"""Find all commits matching the given criteria.
|
308 |
-
|
309 |
-
:param repo:
|
310 |
-
The :class:`~git.repo.base.Repo`.
|
311 |
-
|
312 |
-
:param rev:
|
313 |
-
Revision specifier. See :manpage:`git-rev-parse(1)` for viable options.
|
314 |
-
|
315 |
-
:param paths:
|
316 |
-
An optional path or list of paths. If set only :class:`Commit`\s that
|
317 |
-
include the path or paths will be considered.
|
318 |
-
|
319 |
-
:param kwargs:
|
320 |
-
Optional keyword arguments to :manpage:`git-rev-list(1)` where:
|
321 |
-
|
322 |
-
* ``max_count`` is the maximum number of commits to fetch.
|
323 |
-
* ``skip`` is the number of commits to skip.
|
324 |
-
* ``since`` selects all commits since some date, e.g. ``"1970-01-01"``.
|
325 |
-
|
326 |
-
:return:
|
327 |
-
Iterator yielding :class:`Commit` items.
|
328 |
-
"""
|
329 |
-
if "pretty" in kwargs:
|
330 |
-
raise ValueError("--pretty cannot be used as parsing expects single sha's only")
|
331 |
-
# END handle pretty
|
332 |
-
|
333 |
-
# Use -- in all cases, to prevent possibility of ambiguous arguments.
|
334 |
-
# See https://github.com/gitpython-developers/GitPython/issues/264.
|
335 |
-
|
336 |
-
args_list: List[PathLike] = ["--"]
|
337 |
-
|
338 |
-
if paths:
|
339 |
-
paths_tup: Tuple[PathLike, ...]
|
340 |
-
if isinstance(paths, (str, os.PathLike)):
|
341 |
-
paths_tup = (paths,)
|
342 |
-
else:
|
343 |
-
paths_tup = tuple(paths)
|
344 |
-
|
345 |
-
args_list.extend(paths_tup)
|
346 |
-
# END if paths
|
347 |
-
|
348 |
-
proc = repo.git.rev_list(rev, args_list, as_process=True, **kwargs)
|
349 |
-
return cls._iter_from_process_or_stream(repo, proc)
|
350 |
-
|
351 |
-
def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any) -> Iterator["Commit"]:
|
352 |
-
R"""Iterate _all_ parents of this commit.
|
353 |
-
|
354 |
-
:param paths:
|
355 |
-
Optional path or list of paths limiting the :class:`Commit`\s to those that
|
356 |
-
contain at least one of the paths.
|
357 |
-
|
358 |
-
:param kwargs:
|
359 |
-
All arguments allowed by :manpage:`git-rev-list(1)`.
|
360 |
-
|
361 |
-
:return:
|
362 |
-
Iterator yielding :class:`Commit` objects which are parents of ``self``
|
363 |
-
"""
|
364 |
-
# skip ourselves
|
365 |
-
skip = kwargs.get("skip", 1)
|
366 |
-
if skip == 0: # skip ourselves
|
367 |
-
skip = 1
|
368 |
-
kwargs["skip"] = skip
|
369 |
-
|
370 |
-
return self.iter_items(self.repo, self, paths, **kwargs)
|
371 |
-
|
372 |
-
@property
|
373 |
-
def stats(self) -> Stats:
|
374 |
-
"""Create a git stat from changes between this commit and its first parent
|
375 |
-
or from all changes done if this is the very first commit.
|
376 |
-
|
377 |
-
:return:
|
378 |
-
:class:`Stats`
|
379 |
-
"""
|
380 |
-
if not self.parents:
|
381 |
-
text = self.repo.git.diff_tree(self.hexsha, "--", numstat=True, no_renames=True, root=True)
|
382 |
-
text2 = ""
|
383 |
-
for line in text.splitlines()[1:]:
|
384 |
-
(insertions, deletions, filename) = line.split("\t")
|
385 |
-
text2 += "%s\t%s\t%s\n" % (insertions, deletions, filename)
|
386 |
-
text = text2
|
387 |
-
else:
|
388 |
-
text = self.repo.git.diff(self.parents[0].hexsha, self.hexsha, "--", numstat=True, no_renames=True)
|
389 |
-
return Stats._list_from_string(self.repo, text)
|
390 |
-
|
391 |
-
@property
|
392 |
-
def trailers(self) -> Dict[str, str]:
|
393 |
-
"""Deprecated. Get the trailers of the message as a dictionary.
|
394 |
-
|
395 |
-
:note:
|
396 |
-
This property is deprecated, please use either :attr:`trailers_list` or
|
397 |
-
:attr:`trailers_dict`.
|
398 |
-
|
399 |
-
:return:
|
400 |
-
Dictionary containing whitespace stripped trailer information.
|
401 |
-
Only contains the latest instance of each trailer key.
|
402 |
-
"""
|
403 |
-
warnings.warn(
|
404 |
-
"Commit.trailers is deprecated, use Commit.trailers_list or Commit.trailers_dict instead",
|
405 |
-
DeprecationWarning,
|
406 |
-
stacklevel=2,
|
407 |
-
)
|
408 |
-
return {k: v[0] for k, v in self.trailers_dict.items()}
|
409 |
-
|
410 |
-
@property
|
411 |
-
def trailers_list(self) -> List[Tuple[str, str]]:
|
412 |
-
"""Get the trailers of the message as a list.
|
413 |
-
|
414 |
-
Git messages can contain trailer information that are similar to :rfc:`822`
|
415 |
-
e-mail headers. See :manpage:`git-interpret-trailers(1)`.
|
416 |
-
|
417 |
-
This function calls ``git interpret-trailers --parse`` onto the message to
|
418 |
-
extract the trailer information, returns the raw trailer data as a list.
|
419 |
-
|
420 |
-
Valid message with trailer::
|
421 |
-
|
422 |
-
Subject line
|
423 |
-
|
424 |
-
some body information
|
425 |
-
|
426 |
-
another information
|
427 |
-
|
428 |
-
key1: value1.1
|
429 |
-
key1: value1.2
|
430 |
-
key2 : value 2 with inner spaces
|
431 |
-
|
432 |
-
Returned list will look like this::
|
433 |
-
|
434 |
-
[
|
435 |
-
("key1", "value1.1"),
|
436 |
-
("key1", "value1.2"),
|
437 |
-
("key2", "value 2 with inner spaces"),
|
438 |
-
]
|
439 |
-
|
440 |
-
:return:
|
441 |
-
List containing key-value tuples of whitespace stripped trailer information.
|
442 |
-
"""
|
443 |
-
cmd = ["git", "interpret-trailers", "--parse"]
|
444 |
-
proc: Git.AutoInterrupt = self.repo.git.execute( # type: ignore[call-overload]
|
445 |
-
cmd,
|
446 |
-
as_process=True,
|
447 |
-
istream=PIPE,
|
448 |
-
)
|
449 |
-
trailer: str = proc.communicate(str(self.message).encode())[0].decode("utf8")
|
450 |
-
trailer = trailer.strip()
|
451 |
-
|
452 |
-
if not trailer:
|
453 |
-
return []
|
454 |
-
|
455 |
-
trailer_list = []
|
456 |
-
for t in trailer.split("\n"):
|
457 |
-
key, val = t.split(":", 1)
|
458 |
-
trailer_list.append((key.strip(), val.strip()))
|
459 |
-
|
460 |
-
return trailer_list
|
461 |
-
|
462 |
-
@property
|
463 |
-
def trailers_dict(self) -> Dict[str, List[str]]:
|
464 |
-
"""Get the trailers of the message as a dictionary.
|
465 |
-
|
466 |
-
Git messages can contain trailer information that are similar to :rfc:`822`
|
467 |
-
e-mail headers. See :manpage:`git-interpret-trailers(1)`.
|
468 |
-
|
469 |
-
This function calls ``git interpret-trailers --parse`` onto the message to
|
470 |
-
extract the trailer information. The key value pairs are stripped of leading and
|
471 |
-
trailing whitespaces before they get saved into a dictionary.
|
472 |
-
|
473 |
-
Valid message with trailer::
|
474 |
-
|
475 |
-
Subject line
|
476 |
-
|
477 |
-
some body information
|
478 |
-
|
479 |
-
another information
|
480 |
-
|
481 |
-
key1: value1.1
|
482 |
-
key1: value1.2
|
483 |
-
key2 : value 2 with inner spaces
|
484 |
-
|
485 |
-
Returned dictionary will look like this::
|
486 |
-
|
487 |
-
{
|
488 |
-
"key1": ["value1.1", "value1.2"],
|
489 |
-
"key2": ["value 2 with inner spaces"],
|
490 |
-
}
|
491 |
-
|
492 |
-
|
493 |
-
:return:
|
494 |
-
Dictionary containing whitespace stripped trailer information, mapping
|
495 |
-
trailer keys to a list of their corresponding values.
|
496 |
-
"""
|
497 |
-
d = defaultdict(list)
|
498 |
-
for key, val in self.trailers_list:
|
499 |
-
d[key].append(val)
|
500 |
-
return dict(d)
|
501 |
-
|
502 |
-
@classmethod
|
503 |
-
def _iter_from_process_or_stream(cls, repo: "Repo", proc_or_stream: Union[Popen, IO]) -> Iterator["Commit"]:
|
504 |
-
"""Parse out commit information into a list of :class:`Commit` objects.
|
505 |
-
|
506 |
-
We expect one line per commit, and parse the actual commit information directly
|
507 |
-
from our lighting fast object database.
|
508 |
-
|
509 |
-
:param proc:
|
510 |
-
:manpage:`git-rev-list(1)` process instance - one sha per line.
|
511 |
-
|
512 |
-
:return:
|
513 |
-
Iterator supplying :class:`Commit` objects
|
514 |
-
"""
|
515 |
-
|
516 |
-
# def is_proc(inp) -> TypeGuard[Popen]:
|
517 |
-
# return hasattr(proc_or_stream, 'wait') and not hasattr(proc_or_stream, 'readline')
|
518 |
-
|
519 |
-
# def is_stream(inp) -> TypeGuard[IO]:
|
520 |
-
# return hasattr(proc_or_stream, 'readline')
|
521 |
-
|
522 |
-
if hasattr(proc_or_stream, "wait"):
|
523 |
-
proc_or_stream = cast(Popen, proc_or_stream)
|
524 |
-
if proc_or_stream.stdout is not None:
|
525 |
-
stream = proc_or_stream.stdout
|
526 |
-
elif hasattr(proc_or_stream, "readline"):
|
527 |
-
proc_or_stream = cast(IO, proc_or_stream) # type: ignore[redundant-cast]
|
528 |
-
stream = proc_or_stream
|
529 |
-
|
530 |
-
readline = stream.readline
|
531 |
-
while True:
|
532 |
-
line = readline()
|
533 |
-
if not line:
|
534 |
-
break
|
535 |
-
hexsha = line.strip()
|
536 |
-
if len(hexsha) > 40:
|
537 |
-
# Split additional information, as returned by bisect for instance.
|
538 |
-
hexsha, _ = line.split(None, 1)
|
539 |
-
# END handle extra info
|
540 |
-
|
541 |
-
assert len(hexsha) == 40, "Invalid line: %s" % hexsha
|
542 |
-
yield cls(repo, hex_to_bin(hexsha))
|
543 |
-
# END for each line in stream
|
544 |
-
|
545 |
-
# TODO: Review this - it seems process handling got a bit out of control due to
|
546 |
-
# many developers trying to fix the open file handles issue.
|
547 |
-
if hasattr(proc_or_stream, "wait"):
|
548 |
-
proc_or_stream = cast(Popen, proc_or_stream)
|
549 |
-
finalize_process(proc_or_stream)
|
550 |
-
|
551 |
-
@classmethod
|
552 |
-
def create_from_tree(
|
553 |
-
cls,
|
554 |
-
repo: "Repo",
|
555 |
-
tree: Union[Tree, str],
|
556 |
-
message: str,
|
557 |
-
parent_commits: Union[None, List["Commit"]] = None,
|
558 |
-
head: bool = False,
|
559 |
-
author: Union[None, Actor] = None,
|
560 |
-
committer: Union[None, Actor] = None,
|
561 |
-
author_date: Union[None, str, datetime.datetime] = None,
|
562 |
-
commit_date: Union[None, str, datetime.datetime] = None,
|
563 |
-
) -> "Commit":
|
564 |
-
"""Commit the given tree, creating a :class:`Commit` object.
|
565 |
-
|
566 |
-
:param repo:
|
567 |
-
:class:`~git.repo.base.Repo` object the commit should be part of.
|
568 |
-
|
569 |
-
:param tree:
|
570 |
-
:class:`~git.objects.tree.Tree` object or hex or bin sha.
|
571 |
-
The tree of the new commit.
|
572 |
-
|
573 |
-
:param message:
|
574 |
-
Commit message. It may be an empty string if no message is provided. It will
|
575 |
-
be converted to a string, in any case.
|
576 |
-
|
577 |
-
:param parent_commits:
|
578 |
-
Optional :class:`Commit` objects to use as parents for the new commit. If
|
579 |
-
empty list, the commit will have no parents at all and become a root commit.
|
580 |
-
If ``None``, the current head commit will be the parent of the new commit
|
581 |
-
object.
|
582 |
-
|
583 |
-
:param head:
|
584 |
-
If ``True``, the HEAD will be advanced to the new commit automatically.
|
585 |
-
Otherwise the HEAD will remain pointing on the previous commit. This could
|
586 |
-
lead to undesired results when diffing files.
|
587 |
-
|
588 |
-
:param author:
|
589 |
-
The name of the author, optional.
|
590 |
-
If unset, the repository configuration is used to obtain this value.
|
591 |
-
|
592 |
-
:param committer:
|
593 |
-
The name of the committer, optional.
|
594 |
-
If unset, the repository configuration is used to obtain this value.
|
595 |
-
|
596 |
-
:param author_date:
|
597 |
-
The timestamp for the author field.
|
598 |
-
|
599 |
-
:param commit_date:
|
600 |
-
The timestamp for the committer field.
|
601 |
-
|
602 |
-
:return:
|
603 |
-
:class:`Commit` object representing the new commit.
|
604 |
-
|
605 |
-
:note:
|
606 |
-
Additional information about the committer and author are taken from the
|
607 |
-
environment or from the git configuration. See :manpage:`git-commit-tree(1)`
|
608 |
-
for more information.
|
609 |
-
"""
|
610 |
-
if parent_commits is None:
|
611 |
-
try:
|
612 |
-
parent_commits = [repo.head.commit]
|
613 |
-
except ValueError:
|
614 |
-
# Empty repositories have no head commit.
|
615 |
-
parent_commits = []
|
616 |
-
# END handle parent commits
|
617 |
-
else:
|
618 |
-
for p in parent_commits:
|
619 |
-
if not isinstance(p, cls):
|
620 |
-
raise ValueError(f"Parent commit '{p!r}' must be of type {cls}")
|
621 |
-
# END check parent commit types
|
622 |
-
# END if parent commits are unset
|
623 |
-
|
624 |
-
# Retrieve all additional information, create a commit object, and serialize it.
|
625 |
-
# Generally:
|
626 |
-
# * Environment variables override configuration values.
|
627 |
-
# * Sensible defaults are set according to the git documentation.
|
628 |
-
|
629 |
-
# COMMITTER AND AUTHOR INFO
|
630 |
-
cr = repo.config_reader()
|
631 |
-
env = os.environ
|
632 |
-
|
633 |
-
committer = committer or Actor.committer(cr)
|
634 |
-
author = author or Actor.author(cr)
|
635 |
-
|
636 |
-
# PARSE THE DATES
|
637 |
-
unix_time = int(time())
|
638 |
-
is_dst = daylight and localtime().tm_isdst > 0
|
639 |
-
offset = altzone if is_dst else timezone
|
640 |
-
|
641 |
-
author_date_str = env.get(cls.env_author_date, "")
|
642 |
-
if author_date:
|
643 |
-
author_time, author_offset = parse_date(author_date)
|
644 |
-
elif author_date_str:
|
645 |
-
author_time, author_offset = parse_date(author_date_str)
|
646 |
-
else:
|
647 |
-
author_time, author_offset = unix_time, offset
|
648 |
-
# END set author time
|
649 |
-
|
650 |
-
committer_date_str = env.get(cls.env_committer_date, "")
|
651 |
-
if commit_date:
|
652 |
-
committer_time, committer_offset = parse_date(commit_date)
|
653 |
-
elif committer_date_str:
|
654 |
-
committer_time, committer_offset = parse_date(committer_date_str)
|
655 |
-
else:
|
656 |
-
committer_time, committer_offset = unix_time, offset
|
657 |
-
# END set committer time
|
658 |
-
|
659 |
-
# Assume UTF-8 encoding.
|
660 |
-
enc_section, enc_option = cls.conf_encoding.split(".")
|
661 |
-
conf_encoding = cr.get_value(enc_section, enc_option, cls.default_encoding)
|
662 |
-
if not isinstance(conf_encoding, str):
|
663 |
-
raise TypeError("conf_encoding could not be coerced to str")
|
664 |
-
|
665 |
-
# If the tree is no object, make sure we create one - otherwise the created
|
666 |
-
# commit object is invalid.
|
667 |
-
if isinstance(tree, str):
|
668 |
-
tree = repo.tree(tree)
|
669 |
-
# END tree conversion
|
670 |
-
|
671 |
-
# CREATE NEW COMMIT
|
672 |
-
new_commit = cls(
|
673 |
-
repo,
|
674 |
-
cls.NULL_BIN_SHA,
|
675 |
-
tree,
|
676 |
-
author,
|
677 |
-
author_time,
|
678 |
-
author_offset,
|
679 |
-
committer,
|
680 |
-
committer_time,
|
681 |
-
committer_offset,
|
682 |
-
message,
|
683 |
-
parent_commits,
|
684 |
-
conf_encoding,
|
685 |
-
)
|
686 |
-
|
687 |
-
new_commit.binsha = cls._calculate_sha_(repo, new_commit)
|
688 |
-
|
689 |
-
if head:
|
690 |
-
# Need late import here, importing git at the very beginning throws as
|
691 |
-
# well...
|
692 |
-
import git.refs
|
693 |
-
|
694 |
-
try:
|
695 |
-
repo.head.set_commit(new_commit, logmsg=message)
|
696 |
-
except ValueError:
|
697 |
-
# head is not yet set to the ref our HEAD points to.
|
698 |
-
# Happens on first commit.
|
699 |
-
master = git.refs.Head.create(
|
700 |
-
repo,
|
701 |
-
repo.head.ref,
|
702 |
-
new_commit,
|
703 |
-
logmsg="commit (initial): %s" % message,
|
704 |
-
)
|
705 |
-
repo.head.set_reference(master, logmsg="commit: Switching to %s" % master)
|
706 |
-
# END handle empty repositories
|
707 |
-
# END advance head handling
|
708 |
-
|
709 |
-
return new_commit
|
710 |
-
|
711 |
-
# { Serializable Implementation
|
712 |
-
|
713 |
-
def _serialize(self, stream: BytesIO) -> "Commit":
|
714 |
-
write = stream.write
|
715 |
-
write(("tree %s\n" % self.tree).encode("ascii"))
|
716 |
-
for p in self.parents:
|
717 |
-
write(("parent %s\n" % p).encode("ascii"))
|
718 |
-
|
719 |
-
a = self.author
|
720 |
-
aname = a.name
|
721 |
-
c = self.committer
|
722 |
-
fmt = "%s %s <%s> %s %s\n"
|
723 |
-
write(
|
724 |
-
(
|
725 |
-
fmt
|
726 |
-
% (
|
727 |
-
"author",
|
728 |
-
aname,
|
729 |
-
a.email,
|
730 |
-
self.authored_date,
|
731 |
-
altz_to_utctz_str(self.author_tz_offset),
|
732 |
-
)
|
733 |
-
).encode(self.encoding)
|
734 |
-
)
|
735 |
-
|
736 |
-
# Encode committer.
|
737 |
-
aname = c.name
|
738 |
-
write(
|
739 |
-
(
|
740 |
-
fmt
|
741 |
-
% (
|
742 |
-
"committer",
|
743 |
-
aname,
|
744 |
-
c.email,
|
745 |
-
self.committed_date,
|
746 |
-
altz_to_utctz_str(self.committer_tz_offset),
|
747 |
-
)
|
748 |
-
).encode(self.encoding)
|
749 |
-
)
|
750 |
-
|
751 |
-
if self.encoding != self.default_encoding:
|
752 |
-
write(("encoding %s\n" % self.encoding).encode("ascii"))
|
753 |
-
|
754 |
-
try:
|
755 |
-
if self.__getattribute__("gpgsig"):
|
756 |
-
write(b"gpgsig")
|
757 |
-
for sigline in self.gpgsig.rstrip("\n").split("\n"):
|
758 |
-
write((" " + sigline + "\n").encode("ascii"))
|
759 |
-
except AttributeError:
|
760 |
-
pass
|
761 |
-
|
762 |
-
write(b"\n")
|
763 |
-
|
764 |
-
# Write plain bytes, be sure its encoded according to our encoding.
|
765 |
-
if isinstance(self.message, str):
|
766 |
-
write(self.message.encode(self.encoding))
|
767 |
-
else:
|
768 |
-
write(self.message)
|
769 |
-
# END handle encoding
|
770 |
-
return self
|
771 |
-
|
772 |
-
def _deserialize(self, stream: BytesIO) -> "Commit":
|
773 |
-
readline = stream.readline
|
774 |
-
self.tree = Tree(self.repo, hex_to_bin(readline().split()[1]), Tree.tree_id << 12, "")
|
775 |
-
|
776 |
-
self.parents = []
|
777 |
-
next_line = None
|
778 |
-
while True:
|
779 |
-
parent_line = readline()
|
780 |
-
if not parent_line.startswith(b"parent"):
|
781 |
-
next_line = parent_line
|
782 |
-
break
|
783 |
-
# END abort reading parents
|
784 |
-
self.parents.append(type(self)(self.repo, hex_to_bin(parent_line.split()[-1].decode("ascii"))))
|
785 |
-
# END for each parent line
|
786 |
-
self.parents = tuple(self.parents)
|
787 |
-
|
788 |
-
# We don't know actual author encoding before we have parsed it, so keep the
|
789 |
-
# lines around.
|
790 |
-
author_line = next_line
|
791 |
-
committer_line = readline()
|
792 |
-
|
793 |
-
# We might run into one or more mergetag blocks, skip those for now.
|
794 |
-
next_line = readline()
|
795 |
-
while next_line.startswith(b"mergetag "):
|
796 |
-
next_line = readline()
|
797 |
-
while next_line.startswith(b" "):
|
798 |
-
next_line = readline()
|
799 |
-
# END skip mergetags
|
800 |
-
|
801 |
-
# Now we can have the encoding line, or an empty line followed by the optional
|
802 |
-
# message.
|
803 |
-
self.encoding = self.default_encoding
|
804 |
-
self.gpgsig = ""
|
805 |
-
|
806 |
-
# Read headers.
|
807 |
-
enc = next_line
|
808 |
-
buf = enc.strip()
|
809 |
-
while buf:
|
810 |
-
if buf[0:10] == b"encoding ":
|
811 |
-
self.encoding = buf[buf.find(b" ") + 1 :].decode(self.encoding, "ignore")
|
812 |
-
elif buf[0:7] == b"gpgsig ":
|
813 |
-
sig = buf[buf.find(b" ") + 1 :] + b"\n"
|
814 |
-
is_next_header = False
|
815 |
-
while True:
|
816 |
-
sigbuf = readline()
|
817 |
-
if not sigbuf:
|
818 |
-
break
|
819 |
-
if sigbuf[0:1] != b" ":
|
820 |
-
buf = sigbuf.strip()
|
821 |
-
is_next_header = True
|
822 |
-
break
|
823 |
-
sig += sigbuf[1:]
|
824 |
-
# END read all signature
|
825 |
-
self.gpgsig = sig.rstrip(b"\n").decode(self.encoding, "ignore")
|
826 |
-
if is_next_header:
|
827 |
-
continue
|
828 |
-
buf = readline().strip()
|
829 |
-
|
830 |
-
# Decode the author's name.
|
831 |
-
try:
|
832 |
-
(
|
833 |
-
self.author,
|
834 |
-
self.authored_date,
|
835 |
-
self.author_tz_offset,
|
836 |
-
) = parse_actor_and_date(author_line.decode(self.encoding, "replace"))
|
837 |
-
except UnicodeDecodeError:
|
838 |
-
_logger.error(
|
839 |
-
"Failed to decode author line '%s' using encoding %s",
|
840 |
-
author_line,
|
841 |
-
self.encoding,
|
842 |
-
exc_info=True,
|
843 |
-
)
|
844 |
-
|
845 |
-
try:
|
846 |
-
(
|
847 |
-
self.committer,
|
848 |
-
self.committed_date,
|
849 |
-
self.committer_tz_offset,
|
850 |
-
) = parse_actor_and_date(committer_line.decode(self.encoding, "replace"))
|
851 |
-
except UnicodeDecodeError:
|
852 |
-
_logger.error(
|
853 |
-
"Failed to decode committer line '%s' using encoding %s",
|
854 |
-
committer_line,
|
855 |
-
self.encoding,
|
856 |
-
exc_info=True,
|
857 |
-
)
|
858 |
-
# END handle author's encoding
|
859 |
-
|
860 |
-
# A stream from our data simply gives us the plain message.
|
861 |
-
# The end of our message stream is marked with a newline that we strip.
|
862 |
-
self.message = stream.read()
|
863 |
-
try:
|
864 |
-
self.message = self.message.decode(self.encoding, "replace")
|
865 |
-
except UnicodeDecodeError:
|
866 |
-
_logger.error(
|
867 |
-
"Failed to decode message '%s' using encoding %s",
|
868 |
-
self.message,
|
869 |
-
self.encoding,
|
870 |
-
exc_info=True,
|
871 |
-
)
|
872 |
-
# END exception handling
|
873 |
-
|
874 |
-
return self
|
875 |
-
|
876 |
-
# } END serializable implementation
|
877 |
-
|
878 |
-
@property
|
879 |
-
def co_authors(self) -> List[Actor]:
|
880 |
-
"""Search the commit message for any co-authors of this commit.
|
881 |
-
|
882 |
-
Details on co-authors:
|
883 |
-
https://github.blog/2018-01-29-commit-together-with-co-authors/
|
884 |
-
|
885 |
-
:return:
|
886 |
-
List of co-authors for this commit (as :class:`~git.util.Actor` objects).
|
887 |
-
"""
|
888 |
-
co_authors = []
|
889 |
-
|
890 |
-
if self.message:
|
891 |
-
results = re.findall(
|
892 |
-
r"^Co-authored-by: (.*) <(.*?)>$",
|
893 |
-
self.message,
|
894 |
-
re.MULTILINE,
|
895 |
-
)
|
896 |
-
for author in results:
|
897 |
-
co_authors.append(Actor(*author))
|
898 |
-
|
899 |
-
return co_authors
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|