GitPython 3.1.56__tar.gz → 3.1.58__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. {gitpython-3.1.56 → gitpython-3.1.58/GitPython.egg-info}/PKG-INFO +5 -6
  2. {gitpython-3.1.56 → gitpython-3.1.58}/GitPython.egg-info/requires.txt +3 -0
  3. {gitpython-3.1.56/GitPython.egg-info → gitpython-3.1.58}/PKG-INFO +5 -6
  4. {gitpython-3.1.56 → gitpython-3.1.58}/README.md +3 -5
  5. gitpython-3.1.58/VERSION +1 -0
  6. {gitpython-3.1.56 → gitpython-3.1.58}/doc/source/changes.rst +33 -1
  7. {gitpython-3.1.56 → gitpython-3.1.58}/doc/source/intro.rst +0 -5
  8. {gitpython-3.1.56 → gitpython-3.1.58}/doc/source/reference.rst +1 -0
  9. {gitpython-3.1.56 → gitpython-3.1.58}/git/__init__.py +1 -1
  10. {gitpython-3.1.56 → gitpython-3.1.58}/git/cmd.py +44 -14
  11. {gitpython-3.1.56 → gitpython-3.1.58}/git/config.py +12 -4
  12. {gitpython-3.1.56 → gitpython-3.1.58}/git/diff.py +4 -1
  13. {gitpython-3.1.56 → gitpython-3.1.58}/git/index/base.py +76 -16
  14. {gitpython-3.1.56 → gitpython-3.1.58}/git/index/fun.py +100 -2
  15. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/commit.py +5 -2
  16. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/fun.py +2 -2
  17. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/submodule/base.py +32 -18
  18. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/submodule/root.py +3 -2
  19. {gitpython-3.1.56 → gitpython-3.1.58}/git/refs/head.py +26 -1
  20. {gitpython-3.1.56 → gitpython-3.1.58}/git/refs/log.py +1 -0
  21. {gitpython-3.1.56 → gitpython-3.1.58}/git/refs/symbolic.py +1 -1
  22. {gitpython-3.1.56 → gitpython-3.1.58}/git/refs/tag.py +13 -0
  23. {gitpython-3.1.56 → gitpython-3.1.58}/git/repo/base.py +43 -9
  24. {gitpython-3.1.56 → gitpython-3.1.58}/git/util.py +83 -7
  25. {gitpython-3.1.56 → gitpython-3.1.58}/pyproject.toml +15 -7
  26. {gitpython-3.1.56 → gitpython-3.1.58}/setup.py +1 -1
  27. {gitpython-3.1.56 → gitpython-3.1.58}/test-requirements.txt +1 -0
  28. gitpython-3.1.56/VERSION +0 -1
  29. {gitpython-3.1.56 → gitpython-3.1.58}/AUTHORS +0 -0
  30. {gitpython-3.1.56 → gitpython-3.1.58}/CHANGES +0 -0
  31. {gitpython-3.1.56 → gitpython-3.1.58}/CONTRIBUTING.md +0 -0
  32. {gitpython-3.1.56 → gitpython-3.1.58}/GitPython.egg-info/SOURCES.txt +0 -0
  33. {gitpython-3.1.56 → gitpython-3.1.58}/GitPython.egg-info/dependency_links.txt +0 -0
  34. {gitpython-3.1.56 → gitpython-3.1.58}/GitPython.egg-info/not-zip-safe +0 -0
  35. {gitpython-3.1.56 → gitpython-3.1.58}/GitPython.egg-info/top_level.txt +0 -0
  36. {gitpython-3.1.56 → gitpython-3.1.58}/LICENSE +0 -0
  37. {gitpython-3.1.56 → gitpython-3.1.58}/MANIFEST.in +0 -0
  38. {gitpython-3.1.56 → gitpython-3.1.58}/doc/.gitignore +0 -0
  39. {gitpython-3.1.56 → gitpython-3.1.58}/doc/Makefile +0 -0
  40. {gitpython-3.1.56 → gitpython-3.1.58}/doc/requirements.txt +0 -0
  41. {gitpython-3.1.56 → gitpython-3.1.58}/doc/source/conf.py +0 -0
  42. {gitpython-3.1.56 → gitpython-3.1.58}/doc/source/index.rst +0 -0
  43. {gitpython-3.1.56 → gitpython-3.1.58}/doc/source/quickstart.rst +0 -0
  44. {gitpython-3.1.56 → gitpython-3.1.58}/doc/source/roadmap.rst +0 -0
  45. {gitpython-3.1.56 → gitpython-3.1.58}/doc/source/tutorial.rst +0 -0
  46. {gitpython-3.1.56 → gitpython-3.1.58}/git/compat.py +0 -0
  47. {gitpython-3.1.56 → gitpython-3.1.58}/git/db.py +0 -0
  48. {gitpython-3.1.56 → gitpython-3.1.58}/git/exc.py +0 -0
  49. {gitpython-3.1.56 → gitpython-3.1.58}/git/index/__init__.py +0 -0
  50. {gitpython-3.1.56 → gitpython-3.1.58}/git/index/typ.py +0 -0
  51. {gitpython-3.1.56 → gitpython-3.1.58}/git/index/util.py +0 -0
  52. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/__init__.py +0 -0
  53. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/base.py +0 -0
  54. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/blob.py +0 -0
  55. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/submodule/__init__.py +0 -0
  56. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/submodule/util.py +0 -0
  57. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/tag.py +0 -0
  58. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/tree.py +0 -0
  59. {gitpython-3.1.56 → gitpython-3.1.58}/git/objects/util.py +0 -0
  60. {gitpython-3.1.56 → gitpython-3.1.58}/git/py.typed +0 -0
  61. {gitpython-3.1.56 → gitpython-3.1.58}/git/refs/__init__.py +0 -0
  62. {gitpython-3.1.56 → gitpython-3.1.58}/git/refs/reference.py +0 -0
  63. {gitpython-3.1.56 → gitpython-3.1.58}/git/refs/remote.py +0 -0
  64. {gitpython-3.1.56 → gitpython-3.1.58}/git/remote.py +0 -0
  65. {gitpython-3.1.56 → gitpython-3.1.58}/git/repo/__init__.py +0 -0
  66. {gitpython-3.1.56 → gitpython-3.1.58}/git/repo/fun.py +0 -0
  67. {gitpython-3.1.56 → gitpython-3.1.58}/git/types.py +0 -0
  68. {gitpython-3.1.56 → gitpython-3.1.58}/requirements.txt +0 -0
  69. {gitpython-3.1.56 → gitpython-3.1.58}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GitPython
3
- Version: 3.1.56
3
+ Version: 3.1.58
4
4
  Summary: GitPython is a Python library used to interact with Git repositories
5
5
  Home-page: https://github.com/gitpython-developers/GitPython
6
6
  Author: Sebastian Thiel, Michael Trier
@@ -33,6 +33,7 @@ Requires-Dist: gitdb<5,>=4.0.1
33
33
  Requires-Dist: typing-extensions>=3.10.0.2; python_version < "3.10"
34
34
  Provides-Extra: test
35
35
  Requires-Dist: coverage[toml]; extra == "test"
36
+ Requires-Dist: basedpyright==1.39.9; (python_version >= "3.9" and sys_platform != "cygwin") and extra == "test"
36
37
  Requires-Dist: ddt!=1.4.3,>=1.1.1; extra == "test"
37
38
  Requires-Dist: mock; python_version < "3.8" and extra == "test"
38
39
  Requires-Dist: mypy==1.18.2; python_version >= "3.9" and extra == "test"
@@ -163,15 +164,13 @@ In the less common case that you do not want to install test dependencies, `pip
163
164
 
164
165
  #### With editable *dependencies* (not preferred, and rarely needed)
165
166
 
166
- 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 immediately 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.
167
-
168
- 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:
167
+ GitPython, [gitdb](gitdb), and [smmap](smmap) live in this repository as independently packaged projects. To work on all three at once, install each one editably in the same virtual environment:
169
168
 
170
169
  ```sh
171
- pip install -e ".[test]" -e git/ext/gitdb -e git/ext/gitdb/gitdb/ext/smmap
170
+ pip install -e smmap -e gitdb -e ".[test]"
172
171
  ```
173
172
 
174
- 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.
173
+ You can use `pip list` to check which packages are installed editably and which are installed normally.
175
174
 
176
175
  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.
177
176
 
@@ -26,3 +26,6 @@ mock
26
26
 
27
27
  [test:python_version >= "3.9"]
28
28
  mypy==1.18.2
29
+
30
+ [test:python_version >= "3.9" and sys_platform != "cygwin"]
31
+ basedpyright==1.39.9
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GitPython
3
- Version: 3.1.56
3
+ Version: 3.1.58
4
4
  Summary: GitPython is a Python library used to interact with Git repositories
5
5
  Home-page: https://github.com/gitpython-developers/GitPython
6
6
  Author: Sebastian Thiel, Michael Trier
@@ -33,6 +33,7 @@ Requires-Dist: gitdb<5,>=4.0.1
33
33
  Requires-Dist: typing-extensions>=3.10.0.2; python_version < "3.10"
34
34
  Provides-Extra: test
35
35
  Requires-Dist: coverage[toml]; extra == "test"
36
+ Requires-Dist: basedpyright==1.39.9; (python_version >= "3.9" and sys_platform != "cygwin") and extra == "test"
36
37
  Requires-Dist: ddt!=1.4.3,>=1.1.1; extra == "test"
37
38
  Requires-Dist: mock; python_version < "3.8" and extra == "test"
38
39
  Requires-Dist: mypy==1.18.2; python_version >= "3.9" and extra == "test"
@@ -163,15 +164,13 @@ In the less common case that you do not want to install test dependencies, `pip
163
164
 
164
165
  #### With editable *dependencies* (not preferred, and rarely needed)
165
166
 
166
- 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 immediately 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.
167
-
168
- 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:
167
+ GitPython, [gitdb](gitdb), and [smmap](smmap) live in this repository as independently packaged projects. To work on all three at once, install each one editably in the same virtual environment:
169
168
 
170
169
  ```sh
171
- pip install -e ".[test]" -e git/ext/gitdb -e git/ext/gitdb/gitdb/ext/smmap
170
+ pip install -e smmap -e gitdb -e ".[test]"
172
171
  ```
173
172
 
174
- 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.
173
+ You can use `pip list` to check which packages are installed editably and which are installed normally.
175
174
 
176
175
  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.
177
176
 
@@ -101,15 +101,13 @@ In the less common case that you do not want to install test dependencies, `pip
101
101
 
102
102
  #### With editable *dependencies* (not preferred, and rarely needed)
103
103
 
104
- 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 immediately 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.
105
-
106
- 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:
104
+ GitPython, [gitdb](gitdb), and [smmap](smmap) live in this repository as independently packaged projects. To work on all three at once, install each one editably in the same virtual environment:
107
105
 
108
106
  ```sh
109
- pip install -e ".[test]" -e git/ext/gitdb -e git/ext/gitdb/gitdb/ext/smmap
107
+ pip install -e smmap -e gitdb -e ".[test]"
110
108
  ```
111
109
 
112
- 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.
110
+ You can use `pip list` to check which packages are installed editably and which are installed normally.
113
111
 
114
112
  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.
115
113
 
@@ -0,0 +1 @@
1
+ 3.1.58
@@ -2,6 +2,38 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ 3.1.58
6
+ ======
7
+
8
+ Security fixes for
9
+
10
+ * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-hh9p-6wh2-4mfc
11
+ * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-9rj7-rf2p-w77r
12
+ * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-4gmw-gg2m-w46p
13
+ * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-wvpp-8hx9-p66j
14
+ * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-jm78-9fvv-mhgr
15
+ * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-hmq2-w58f-27jc
16
+
17
+ If you can, also try and provide feedback on the upcoming v4 branch
18
+ https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
19
+
20
+ See the following for all changes.
21
+ https://github.com/gitpython-developers/GitPython/releases/tag/3.1.58
22
+
23
+ 3.1.57
24
+ ======
25
+
26
+ A security fix for
27
+
28
+ * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-3f7w-8rr8-f37f
29
+ * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-539m-9xh6-q6rr
30
+
31
+ If you can, also try and provide feedback on the upcoming v4 branch
32
+ https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
33
+
34
+ See the following for all changes.
35
+ https://github.com/gitpython-developers/GitPython/releases/tag/3.1.57
36
+
5
37
  3.1.56
6
38
  ======
7
39
 
@@ -13,7 +45,7 @@ If you can, also try and provide feedback on the upcoming v4 branch
13
45
  https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
14
46
 
15
47
  See the following for all changes.
16
- https://github.com/gitpython-developers/GitPython/releases/tag/3.1.55
48
+ https://github.com/gitpython-developers/GitPython/releases/tag/3.1.56
17
49
 
18
50
  3.1.55
19
51
  ======
@@ -97,11 +97,6 @@ and cloned using::
97
97
 
98
98
  $ git clone https://github.com/gitpython-developers/GitPython git-python
99
99
 
100
- Initialize all submodules to obtain the required dependencies with::
101
-
102
- $ cd git-python
103
- $ git submodule update --init --recursive
104
-
105
100
  Finally verify the installation by running unit tests::
106
101
 
107
102
  $ python -m unittest
@@ -91,6 +91,7 @@ Objects.Util
91
91
  .. automodule:: git.objects.util
92
92
  :members:
93
93
  :undoc-members:
94
+ :private-members: _get_intermediate_items, _list_traverse, _traverse
94
95
  :special-members:
95
96
 
96
97
  Index.Base
@@ -86,7 +86,7 @@ __all__ = [
86
86
  "to_hex_sha",
87
87
  ]
88
88
 
89
- __version__ = '3.1.56'
89
+ __version__ = '3.1.58'
90
90
 
91
91
  from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Tuple, Union
92
92
 
@@ -654,6 +654,12 @@ class Git(metaclass=_GitMeta):
654
654
  "--upload-pack",
655
655
  ]
656
656
 
657
+ unsafe_git_pathspec_from_file_options = [
658
+ # Reads pathspecs from a caller-controlled file. Some commands include an
659
+ # unmatched pathspec in their error output, which can disclose the file.
660
+ "--pathspec-from-file",
661
+ ]
662
+
657
663
  def __getstate__(self) -> Dict[str, Any]:
658
664
  return slots_to_dict(self, exclude=self._excluded_)
659
665
 
@@ -1044,13 +1050,21 @@ class Git(metaclass=_GitMeta):
1044
1050
  values = value if isinstance(value, (list, tuple)) else (value,)
1045
1051
  if any(value is True or (value is not False and value is not None) for value in values):
1046
1052
  key = str(key)
1047
- options.append(f"-{key}" if len(key) == 1 else f"--{dashify(key)}")
1048
- if len(key) == 1 and split_single_char_options:
1053
+ if len(key) != 1:
1054
+ options.append(f"--{dashify(key)}")
1055
+ elif split_single_char_options:
1056
+ options.append(f"-{key}")
1049
1057
  options.extend(
1050
1058
  str(value)
1051
1059
  for value in values
1052
1060
  if value is not True and value not in (False, None) and str(value).startswith("-")
1053
1061
  )
1062
+ else:
1063
+ options.extend(
1064
+ f"-{key}" if value is True else f"-{key}{value}"
1065
+ for value in values
1066
+ if value is True or (value is not False and value is not None)
1067
+ )
1054
1068
  return options
1055
1069
 
1056
1070
  AutoInterrupt: TypeAlias = _AutoInterrupt
@@ -1435,6 +1449,10 @@ class Git(metaclass=_GitMeta):
1435
1449
  if as_process:
1436
1450
  return self.AutoInterrupt(proc, command)
1437
1451
 
1452
+ watchdog: Optional[threading.Timer] = None
1453
+ kill_check: Optional[threading.Event] = None
1454
+ timeout_error: Optional[Callable[[], Union[str, bytes]]] = None
1455
+
1438
1456
  if sys.platform != "win32" and kill_after_timeout is not None:
1439
1457
  # Help mypy figure out this is not None even when used inside communicate().
1440
1458
  timeout = kill_after_timeout
@@ -1460,6 +1478,7 @@ class Git(metaclass=_GitMeta):
1460
1478
  except OSError:
1461
1479
  pass
1462
1480
  # Tell the main routine that the process was killed.
1481
+ assert kill_check is not None
1463
1482
  kill_check.set()
1464
1483
  except OSError:
1465
1484
  # It is possible that the process gets completed in the duration
@@ -1467,23 +1486,25 @@ class Git(metaclass=_GitMeta):
1467
1486
  pass
1468
1487
  return
1469
1488
 
1489
+ def make_timeout_error() -> Union[str, bytes]:
1490
+ err = f'Timeout: the command "{" ".join(redacted_command)}" did not complete in {timeout:g} secs.'
1491
+ return err if universal_newlines else err.encode(defenc)
1492
+
1470
1493
  def communicate() -> Tuple[AnyStr, AnyStr]:
1494
+ assert watchdog is not None
1495
+ assert kill_check is not None
1471
1496
  watchdog.start()
1472
1497
  out, err = proc.communicate()
1473
1498
  watchdog.cancel()
1474
1499
  if kill_check.is_set():
1475
- err = 'Timeout: the command "%s" did not complete in %d secs.' % (
1476
- " ".join(redacted_command),
1477
- timeout,
1478
- )
1479
- if not universal_newlines:
1480
- err = err.encode(defenc)
1500
+ err = make_timeout_error()
1481
1501
  return out, err
1482
1502
 
1483
1503
  # END helpers
1484
1504
 
1485
1505
  kill_check = threading.Event()
1486
1506
  watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
1507
+ timeout_error = make_timeout_error
1487
1508
  else:
1488
1509
  communicate = proc.communicate
1489
1510
 
@@ -1504,15 +1525,24 @@ class Git(metaclass=_GitMeta):
1504
1525
  status = proc.returncode
1505
1526
  else:
1506
1527
  max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
1507
- if proc.stdout is not None:
1508
- stream_copy(proc.stdout, output_stream, max_chunk_size)
1509
- stdout_value = proc.stdout.read()
1510
- if proc.stderr is not None:
1511
- stderr_value = proc.stderr.read()
1528
+ if watchdog is not None:
1529
+ watchdog.start()
1530
+ try:
1531
+ if proc.stdout is not None:
1532
+ stream_copy(proc.stdout, output_stream, max_chunk_size)
1533
+ stdout_value = proc.stdout.read()
1534
+ if proc.stderr is not None:
1535
+ stderr_value = proc.stderr.read()
1536
+ status = proc.wait()
1537
+ finally:
1538
+ if watchdog is not None:
1539
+ watchdog.cancel()
1512
1540
  # Strip trailing "\n".
1513
1541
  if stderr_value is not None and stderr_value.endswith(newline): # type: ignore[arg-type]
1514
1542
  stderr_value = stderr_value[:-1]
1515
- status = proc.wait()
1543
+ if kill_check is not None and kill_check.is_set():
1544
+ assert timeout_error is not None
1545
+ stderr_value = timeout_error()
1516
1546
  # END stdout handling
1517
1547
  finally:
1518
1548
  if proc.stdout is not None:
@@ -75,6 +75,9 @@ See: https://git-scm.com/docs/git-config#_conditional_includes
75
75
  UNSAFE_CONFIG_CHARS_RE = re.compile(r"[\r\n\x00]")
76
76
  """Characters that cannot be safely written in config names or values."""
77
77
 
78
+ VALID_CONFIG_OPTION_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]+$")
79
+ """Pattern for option names that can be written without changing config syntax."""
80
+
78
81
 
79
82
  class MetaParserBuilder(abc.ABCMeta): # noqa: B024
80
83
  """Utility class wrapping base-class methods into decorators that assure read-only
@@ -577,8 +580,12 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
577
580
 
578
581
  if keyword in ["gitdir", "gitdir/i"]:
579
582
  value = osp.expanduser(value)
583
+ git_dir = os.fspath(self._repo.git_dir) if self._repo.git_dir else None
584
+ if sys.platform == "win32":
585
+ git_dir = git_dir.replace("\\", "/") if git_dir else None
580
586
 
581
- if not any(value.startswith(s) for s in ["./", "/"]):
587
+ drive, _tail = osp.splitdrive(value)
588
+ if not drive and not any(value.startswith(s) for s in ["./", "/"]):
582
589
  value = "**/" + value
583
590
  if value.endswith("/"):
584
591
  value += "**"
@@ -590,9 +597,8 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
590
597
  lambda m: f"[{m.group().lower()!r}{m.group().upper()!r}]",
591
598
  value,
592
599
  )
593
- if self._repo.git_dir:
594
- if fnmatch.fnmatchcase(os.fspath(self._repo.git_dir), value):
595
- paths += _all_items(section)
600
+ if git_dir and fnmatch.fnmatchcase(git_dir, value):
601
+ paths += _all_items(section)
596
602
 
597
603
  elif keyword == "onbranch":
598
604
  try:
@@ -897,6 +903,8 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
897
903
  def _assure_config_name_safe(self, name: "cp._SectionName", label: str) -> None:
898
904
  if isinstance(name, str) and UNSAFE_CONFIG_CHARS_RE.search(name):
899
905
  raise ValueError("Git config %s names must not contain CR, LF, or NUL" % label)
906
+ if label == "option" and isinstance(name, str) and not VALID_CONFIG_OPTION_NAME_RE.fullmatch(name):
907
+ raise ValueError("Git config option names may contain only letters, digits, '-', '_', or '.'")
900
908
  if label == "section" and isinstance(name, str):
901
909
  in_quotes = False
902
910
  escaped = False
@@ -284,7 +284,10 @@ class Diffable:
284
284
  # END paths handling
285
285
 
286
286
  kwargs["as_process"] = True
287
- proc = diff_cmd(*self._process_diff_args(args), **kwargs)
287
+ args = self._process_diff_args(args)
288
+ if create_patch:
289
+ self.repo.git(c="diff.mnemonicPrefix=false")
290
+ proc = diff_cmd(*args, **kwargs)
288
291
 
289
292
  diff_method = Diff._index_from_patch_format if create_patch else Diff._index_from_raw_format
290
293
  index = diff_method(self.repo, proc)
@@ -34,6 +34,8 @@ from git.util import (
34
34
  LockedFD,
35
35
  join_path_native,
36
36
  file_contents_ro,
37
+ _is_path_rooted,
38
+ _to_relative_path,
37
39
  to_native_path_linux,
38
40
  unbare_repo,
39
41
  to_bin_sha,
@@ -58,6 +60,7 @@ from typing import (
58
60
  Any,
59
61
  BinaryIO,
60
62
  Callable,
63
+ cast,
61
64
  Dict,
62
65
  Generator,
63
66
  IO,
@@ -130,6 +133,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
130
133
  index directly before operating on it using the git command.
131
134
  """
132
135
 
136
+ unsafe_git_checkout_index_options = ["--prefix"]
137
+ unsafe_git_read_tree_options = ["--index-output"]
138
+
133
139
  __slots__ = ("repo", "version", "entries", "_extension_data", "_file_path")
134
140
 
135
141
  _VERSION = 2
@@ -254,7 +260,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
254
260
 
255
261
  @post_clear_cache
256
262
  @default_index
257
- def merge_tree(self, rhs: Treeish, base: Union[None, Treeish] = None) -> "IndexFile":
263
+ def merge_tree(
264
+ self,
265
+ rhs: Treeish,
266
+ base: Union[None, Treeish] = None,
267
+ allow_unsafe_options: bool = False,
268
+ ) -> "IndexFile":
258
269
  """Merge the given `rhs` treeish into the current index, possibly taking
259
270
  a common base treeish into account.
260
271
 
@@ -268,6 +279,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
268
279
  Optional treeish reference pointing to the common base of `rhs` and this
269
280
  index which equals lhs.
270
281
 
282
+ :param allow_unsafe_options:
283
+ Allow options that may write to arbitrary paths.
284
+
271
285
  :return:
272
286
  self (containing the merge and possibly unmerged entries in case of
273
287
  conflicts)
@@ -278,6 +292,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
278
292
  yourself, you have to commit the changed index (or make a valid tree from
279
293
  it) and retry with a three-way :meth:`index.from_tree <from_tree>` call.
280
294
  """
295
+ if not allow_unsafe_options:
296
+ Git.check_unsafe_options(
297
+ options=Git._option_candidates([base, rhs]),
298
+ unsafe_options=self.unsafe_git_read_tree_options,
299
+ )
300
+
281
301
  # -i : ignore working tree status
282
302
  # --aggressive : handle more merge cases
283
303
  # -m : do an actual merge
@@ -322,7 +342,13 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
322
342
  return inst
323
343
 
324
344
  @classmethod
325
- def from_tree(cls, repo: "Repo", *treeish: Treeish, **kwargs: Any) -> "IndexFile":
345
+ def from_tree(
346
+ cls,
347
+ repo: "Repo",
348
+ *treeish: Treeish,
349
+ allow_unsafe_options: bool = False,
350
+ **kwargs: Any,
351
+ ) -> "IndexFile":
326
352
  R"""Merge the given treeish revisions into a new index which is returned.
327
353
  The original index will remain unaltered.
328
354
 
@@ -346,6 +372,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
346
372
  :param kwargs:
347
373
  Additional arguments passed to :manpage:`git-read-tree(1)`.
348
374
 
375
+ :param allow_unsafe_options:
376
+ Allow options that may write to arbitrary paths.
377
+
349
378
  :return:
350
379
  New :class:`IndexFile` instance. It will point to a temporary index location
351
380
  which does not exist anymore. If you intend to write such a merged Index,
@@ -363,6 +392,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
363
392
  if len(treeish) == 0 or len(treeish) > 3:
364
393
  raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish))
365
394
 
395
+ if not allow_unsafe_options:
396
+ Git.check_unsafe_options(
397
+ options=Git._option_candidates(treeish, kwargs),
398
+ unsafe_options=cls.unsafe_git_read_tree_options,
399
+ )
400
+
366
401
  arg_list: List[Union[Treeish, str]] = []
367
402
  # Ignore that the working tree and index possibly are out of date.
368
403
  if len(treeish) > 1:
@@ -653,16 +688,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
653
688
 
654
689
  :raise ValueError:
655
690
  """
656
- if not osp.isabs(path):
657
- return path
658
691
  if self.repo.bare:
659
- raise InvalidGitRepositoryError("require non-bare repository")
660
- if not osp.normpath(path).startswith(str(self.repo.working_tree_dir)):
661
- raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir))
662
- result = os.path.relpath(path, self.repo.working_tree_dir)
663
- if os.fspath(path).endswith(os.sep) and not result.endswith(os.sep):
664
- result += os.sep
665
- return result
692
+ drive, _tail = osp.splitdrive(os.fspath(path))
693
+ if drive or _is_path_rooted(path):
694
+ raise InvalidGitRepositoryError("paths with a drive or root require a non-bare repository")
695
+ return path
696
+ return _to_relative_path(cast(PathLike, self.repo.working_tree_dir), path)
666
697
 
667
698
  def _preprocess_add_items(
668
699
  self, items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]]
@@ -729,14 +760,17 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
729
760
  ) -> List[BaseIndexEntry]:
730
761
  entries_added: List[BaseIndexEntry] = []
731
762
  if path_rewriter:
763
+ working_tree_dir = self.repo.working_tree_dir
764
+ if working_tree_dir is None:
765
+ raise InvalidGitRepositoryError("Cannot rewrite paths without a working tree")
766
+ working_tree_dir = str(working_tree_dir)
732
767
  for path in paths:
733
768
  if osp.isabs(path):
734
769
  abspath = path
735
- gitrelative_path = path[len(str(self.repo.working_tree_dir)) + 1 :]
770
+ gitrelative_path = path[len(working_tree_dir) + 1 :]
736
771
  else:
737
772
  gitrelative_path = path
738
- if self.repo.working_tree_dir:
739
- abspath = osp.join(self.repo.working_tree_dir, gitrelative_path)
773
+ abspath = osp.join(working_tree_dir, gitrelative_path)
740
774
  # END obtain relative and absolute paths
741
775
 
742
776
  blob = Blob(
@@ -990,6 +1024,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
990
1024
  self,
991
1025
  items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]],
992
1026
  working_tree: bool = False,
1027
+ allow_unsafe_options: bool = False,
993
1028
  **kwargs: Any,
994
1029
  ) -> List[str]:
995
1030
  R"""Remove the given items from the index and optionally from the working tree
@@ -1020,6 +1055,10 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1020
1055
  physically removing the respective file. This may fail if there are
1021
1056
  uncommitted changes in it.
1022
1057
 
1058
+ :param allow_unsafe_options:
1059
+ Allow unsafe options such as ``--pathspec-from-file`` to be passed to
1060
+ :manpage:`git-rm(1)`.
1061
+
1023
1062
  :param kwargs:
1024
1063
  Additional keyword arguments to be passed to :manpage:`git-rm(1)`, such as
1025
1064
  ``r`` to allow recursive removal.
@@ -1031,6 +1070,11 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1031
1070
  This is interesting to know in case you have provided a directory or globs.
1032
1071
  Paths are relative to the repository.
1033
1072
  """
1073
+ if not allow_unsafe_options:
1074
+ Git.check_unsafe_options(
1075
+ options=Git._option_candidates([], kwargs),
1076
+ unsafe_options=Git.unsafe_git_pathspec_from_file_options,
1077
+ )
1034
1078
  args = []
1035
1079
  if not working_tree:
1036
1080
  args.append("--cached")
@@ -1212,6 +1256,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1212
1256
  paths: Union[None, Iterable[PathLike]] = None,
1213
1257
  force: bool = False,
1214
1258
  fprogress: Callable = lambda *args: None,
1259
+ allow_unsafe_options: bool = False,
1215
1260
  **kwargs: Any,
1216
1261
  ) -> Union[None, Iterator[PathLike], Sequence[PathLike]]:
1217
1262
  """Check out the given paths or all files from the version known to the index
@@ -1238,6 +1283,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1238
1283
  no explicit paths are given. Otherwise progress information will be send
1239
1284
  prior and after a file has been checked out.
1240
1285
 
1286
+ :param allow_unsafe_options:
1287
+ Allow unsafe options, such as ``--prefix``.
1288
+
1241
1289
  :param kwargs:
1242
1290
  Additional arguments to be passed to :manpage:`git-checkout-index(1)`.
1243
1291
 
@@ -1261,6 +1309,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1261
1309
  i.e. if you want :manpage:`git-checkout(1)`-like behaviour, use
1262
1310
  ``head.checkout`` instead of ``index.checkout``.
1263
1311
  """
1312
+ if not allow_unsafe_options:
1313
+ Git.check_unsafe_options(
1314
+ options=Git._option_candidates([], kwargs),
1315
+ unsafe_options=self.unsafe_git_checkout_index_options,
1316
+ )
1317
+
1264
1318
  args = ["--index"]
1265
1319
  if force:
1266
1320
  args.append("--force")
@@ -1402,6 +1456,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1402
1456
  working_tree: bool = False,
1403
1457
  paths: Union[None, Iterable[PathLike]] = None,
1404
1458
  head: bool = False,
1459
+ allow_unsafe_options: bool = False,
1405
1460
  **kwargs: Any,
1406
1461
  ) -> "IndexFile":
1407
1462
  """Reset the index to reflect the tree at the given commit. This will not adjust
@@ -1433,6 +1488,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1433
1488
  The paths need to exist at the commit, otherwise an exception will be
1434
1489
  raised.
1435
1490
 
1491
+ :param allow_unsafe_options:
1492
+ Allow options that may write to arbitrary paths.
1493
+
1436
1494
  :param kwargs:
1437
1495
  Additional keyword arguments passed to :manpage:`git-reset(1)`.
1438
1496
 
@@ -1449,15 +1507,15 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1449
1507
  """
1450
1508
  # What we actually want to do is to merge the tree into our existing index,
1451
1509
  # which is what git-read-tree does.
1452
- new_inst = type(self).from_tree(self.repo, commit)
1510
+ new_inst = type(self).from_tree(self.repo, commit, allow_unsafe_options=allow_unsafe_options)
1453
1511
  if not paths:
1454
1512
  self.entries = new_inst.entries
1455
1513
  else:
1456
1514
  nie = new_inst.entries
1457
1515
  for path in paths:
1458
1516
  path = self._to_relative_path(path)
1517
+ key = entry_key(path, 0)
1459
1518
  try:
1460
- key = entry_key(path, 0)
1461
1519
  self.entries[key] = nie[key]
1462
1520
  except KeyError:
1463
1521
  # If key is not in theirs, it mustn't be in ours.
@@ -1547,6 +1605,8 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1547
1605
  args.extend(paths)
1548
1606
 
1549
1607
  kwargs["as_process"] = True
1608
+ if create_patch:
1609
+ self.repo.git(c="diff.mnemonicPrefix=false")
1550
1610
  proc = self.repo.git.diff(*args, **kwargs)
1551
1611
 
1552
1612
  diff_method = (