GitPython 3.1.52__tar.gz → 3.1.54__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.52 → gitpython-3.1.54}/CONTRIBUTING.md +8 -17
  2. {gitpython-3.1.52 → gitpython-3.1.54/GitPython.egg-info}/PKG-INFO +1 -1
  3. {gitpython-3.1.52/GitPython.egg-info → gitpython-3.1.54}/PKG-INFO +1 -1
  4. gitpython-3.1.54/VERSION +1 -0
  5. {gitpython-3.1.52 → gitpython-3.1.54}/doc/source/changes.rst +27 -0
  6. {gitpython-3.1.52 → gitpython-3.1.54}/git/__init__.py +1 -1
  7. {gitpython-3.1.52 → gitpython-3.1.54}/git/cmd.py +8 -1
  8. {gitpython-3.1.52 → gitpython-3.1.54}/git/config.py +16 -0
  9. {gitpython-3.1.52 → gitpython-3.1.54}/git/diff.py +12 -2
  10. {gitpython-3.1.52 → gitpython-3.1.54}/git/index/base.py +16 -2
  11. {gitpython-3.1.52 → gitpython-3.1.54}/git/index/fun.py +20 -3
  12. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/submodule/base.py +141 -100
  13. {gitpython-3.1.52 → gitpython-3.1.54}/git/repo/base.py +5 -0
  14. {gitpython-3.1.52 → gitpython-3.1.54}/git/util.py +28 -18
  15. {gitpython-3.1.52 → gitpython-3.1.54}/pyproject.toml +11 -0
  16. gitpython-3.1.52/VERSION +0 -1
  17. {gitpython-3.1.52 → gitpython-3.1.54}/AUTHORS +0 -0
  18. {gitpython-3.1.52 → gitpython-3.1.54}/CHANGES +0 -0
  19. {gitpython-3.1.52 → gitpython-3.1.54}/GitPython.egg-info/SOURCES.txt +0 -0
  20. {gitpython-3.1.52 → gitpython-3.1.54}/GitPython.egg-info/dependency_links.txt +0 -0
  21. {gitpython-3.1.52 → gitpython-3.1.54}/GitPython.egg-info/not-zip-safe +0 -0
  22. {gitpython-3.1.52 → gitpython-3.1.54}/GitPython.egg-info/requires.txt +0 -0
  23. {gitpython-3.1.52 → gitpython-3.1.54}/GitPython.egg-info/top_level.txt +0 -0
  24. {gitpython-3.1.52 → gitpython-3.1.54}/LICENSE +0 -0
  25. {gitpython-3.1.52 → gitpython-3.1.54}/MANIFEST.in +0 -0
  26. {gitpython-3.1.52 → gitpython-3.1.54}/README.md +0 -0
  27. {gitpython-3.1.52 → gitpython-3.1.54}/doc/.gitignore +0 -0
  28. {gitpython-3.1.52 → gitpython-3.1.54}/doc/Makefile +0 -0
  29. {gitpython-3.1.52 → gitpython-3.1.54}/doc/requirements.txt +0 -0
  30. {gitpython-3.1.52 → gitpython-3.1.54}/doc/source/conf.py +0 -0
  31. {gitpython-3.1.52 → gitpython-3.1.54}/doc/source/index.rst +0 -0
  32. {gitpython-3.1.52 → gitpython-3.1.54}/doc/source/intro.rst +0 -0
  33. {gitpython-3.1.52 → gitpython-3.1.54}/doc/source/quickstart.rst +0 -0
  34. {gitpython-3.1.52 → gitpython-3.1.54}/doc/source/reference.rst +0 -0
  35. {gitpython-3.1.52 → gitpython-3.1.54}/doc/source/roadmap.rst +0 -0
  36. {gitpython-3.1.52 → gitpython-3.1.54}/doc/source/tutorial.rst +0 -0
  37. {gitpython-3.1.52 → gitpython-3.1.54}/git/compat.py +0 -0
  38. {gitpython-3.1.52 → gitpython-3.1.54}/git/db.py +0 -0
  39. {gitpython-3.1.52 → gitpython-3.1.54}/git/exc.py +0 -0
  40. {gitpython-3.1.52 → gitpython-3.1.54}/git/index/__init__.py +0 -0
  41. {gitpython-3.1.52 → gitpython-3.1.54}/git/index/typ.py +0 -0
  42. {gitpython-3.1.52 → gitpython-3.1.54}/git/index/util.py +0 -0
  43. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/__init__.py +0 -0
  44. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/base.py +0 -0
  45. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/blob.py +0 -0
  46. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/commit.py +0 -0
  47. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/fun.py +0 -0
  48. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/submodule/__init__.py +0 -0
  49. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/submodule/root.py +0 -0
  50. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/submodule/util.py +0 -0
  51. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/tag.py +0 -0
  52. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/tree.py +0 -0
  53. {gitpython-3.1.52 → gitpython-3.1.54}/git/objects/util.py +0 -0
  54. {gitpython-3.1.52 → gitpython-3.1.54}/git/py.typed +0 -0
  55. {gitpython-3.1.52 → gitpython-3.1.54}/git/refs/__init__.py +0 -0
  56. {gitpython-3.1.52 → gitpython-3.1.54}/git/refs/head.py +0 -0
  57. {gitpython-3.1.52 → gitpython-3.1.54}/git/refs/log.py +0 -0
  58. {gitpython-3.1.52 → gitpython-3.1.54}/git/refs/reference.py +0 -0
  59. {gitpython-3.1.52 → gitpython-3.1.54}/git/refs/remote.py +0 -0
  60. {gitpython-3.1.52 → gitpython-3.1.54}/git/refs/symbolic.py +0 -0
  61. {gitpython-3.1.52 → gitpython-3.1.54}/git/refs/tag.py +0 -0
  62. {gitpython-3.1.52 → gitpython-3.1.54}/git/remote.py +0 -0
  63. {gitpython-3.1.52 → gitpython-3.1.54}/git/repo/__init__.py +0 -0
  64. {gitpython-3.1.52 → gitpython-3.1.54}/git/repo/fun.py +0 -0
  65. {gitpython-3.1.52 → gitpython-3.1.54}/git/types.py +0 -0
  66. {gitpython-3.1.52 → gitpython-3.1.54}/requirements.txt +0 -0
  67. {gitpython-3.1.52 → gitpython-3.1.54}/setup.cfg +0 -0
  68. {gitpython-3.1.52 → gitpython-3.1.54}/setup.py +0 -0
  69. {gitpython-3.1.52 → gitpython-3.1.54}/test-requirements.txt +0 -0
@@ -20,25 +20,16 @@ A contribution that works only narrowly but lowers the quality of the
20
20
  codebase may be declined. The maintainers may not always be able to provide
21
21
  detailed feedback.
22
22
 
23
- ## AI-assisted contributions
23
+ ## Prevent agent impersonation
24
24
 
25
- If AI edits files for you, disclose it in the pull request description and commit
26
- metadata. Prefer making the agent identity part of the commit, for example by using
27
- an AI author such as `$agent $version <ai-agent@example.invalid>` or a co-author via
28
- a `Co-authored-by: <agent-identity>` trailer.
25
+ AI agents communicating through a person's account must identify themselves, for
26
+ example in issue or PR descriptions and comments. AI assistance that does not replace
27
+ the person as the speaker, such as proofreading or wording polish, does not require
28
+ identification.
29
29
 
30
- Agents operating through a person's GitHub account must identify themselves. For
31
- example, comments posted by an agent should say so directly with phrases like
32
- `AI agent on behalf of <person>: ...`.
33
-
34
- Fully AI-generated comments on pull requests or issues must also be disclosed.
35
- Undisclosed AI-generated comments may lead to the pull request or issue being closed.
36
-
37
- AI-assisted proofreading or wording polish does not need disclosure, but it is still
38
- courteous to mention it when the AI materially influenced the final text.
39
-
40
- Automated or "full-auto" AI contributions without a human responsible for reviewing
41
- and standing behind the work may be closed.
30
+ Attributing AI assistance in commit metadata, for example with a `Co-authored-by`
31
+ trailer, is welcome but not required. Code is reviewed the same way regardless of its
32
+ origin.
42
33
 
43
34
  ## Fuzzing Test Specific Documentation
44
35
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GitPython
3
- Version: 3.1.52
3
+ Version: 3.1.54
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GitPython
3
- Version: 3.1.52
3
+ Version: 3.1.54
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
@@ -0,0 +1 @@
1
+ 3.1.54
@@ -2,6 +2,33 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ 3.1.54
6
+ ======
7
+
8
+ A security fix for
9
+
10
+ * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-r9mr-m37c-5fr3
11
+ * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-6p8h-3wgx-97gf
12
+ * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-fjr4-x663-mwxc#event-857190
13
+
14
+ If you can, also try and provide feedback on the upcoming v4 branch
15
+ https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
16
+
17
+ See the following for all changes.
18
+ https://github.com/gitpython-developers/GitPython/releases/tag/3.1.54
19
+
20
+ 3.1.53
21
+ ======
22
+
23
+ A security fix for
24
+ https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-3rp5-jjmw-4wv2
25
+
26
+ If you can, also try and provide feedback on the upcoming v4 branch
27
+ https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
28
+
29
+ See the following for all changes.
30
+ https://github.com/gitpython-developers/GitPython/releases/tag/3.1.53
31
+
5
32
  3.1.52
6
33
  ======
7
34
 
@@ -86,7 +86,7 @@ __all__ = [
86
86
  "to_hex_sha",
87
87
  ]
88
88
 
89
- __version__ = '3.1.52'
89
+ __version__ = '3.1.54'
90
90
 
91
91
  from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Tuple, Union
92
92
 
@@ -1039,11 +1039,18 @@ class Git(metaclass=_GitMeta):
1039
1039
  option for option in cls._unpack_args([arg for arg in args if arg is not None]) if option.startswith("-")
1040
1040
  ]
1041
1041
  if kwargs:
1042
+ split_single_char_options = kwargs.get("split_single_char_options", True)
1042
1043
  for key, value in kwargs.items():
1043
1044
  values = value if isinstance(value, (list, tuple)) else (value,)
1044
1045
  if any(value is True or (value is not False and value is not None) for value in values):
1045
1046
  key = str(key)
1046
1047
  options.append(f"-{key}" if len(key) == 1 else f"--{dashify(key)}")
1048
+ if len(key) == 1 and split_single_char_options:
1049
+ options.extend(
1050
+ str(value)
1051
+ for value in values
1052
+ if value is not True and value not in (False, None) and str(value).startswith("-")
1053
+ )
1047
1054
  return options
1048
1055
 
1049
1056
  AutoInterrupt: TypeAlias = _AutoInterrupt
@@ -1358,7 +1365,7 @@ class Git(metaclass=_GitMeta):
1358
1365
 
1359
1366
  # Allow the user to have the command executed in their working dir.
1360
1367
  try:
1361
- cwd = self._working_dir or os.getcwd() # type: Union[None, str]
1368
+ cwd = self._working_dir or os.getcwd() # type: Optional[PathLike]
1362
1369
  if not os.access(str(cwd), os.X_OK):
1363
1370
  cwd = None
1364
1371
  except FileNotFoundError:
@@ -897,6 +897,22 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
897
897
  def _assure_config_name_safe(self, name: "cp._SectionName", label: str) -> None:
898
898
  if isinstance(name, str) and UNSAFE_CONFIG_CHARS_RE.search(name):
899
899
  raise ValueError("Git config %s names must not contain CR, LF, or NUL" % label)
900
+ if label == "section" and isinstance(name, str):
901
+ in_quotes = False
902
+ escaped = False
903
+ for index, char in enumerate(name):
904
+ if escaped:
905
+ escaped = False
906
+ elif in_quotes and char == "\\":
907
+ escaped = True
908
+ elif char == '"':
909
+ if not in_quotes and (index == 0 or name[index - 1] not in " \t"):
910
+ raise ValueError("Git config quoted subsection names must begin after whitespace")
911
+ in_quotes = not in_quotes
912
+ elif char == "]" and not in_quotes:
913
+ raise ValueError("Git config section names must not contain an unquoted closing bracket")
914
+ if in_quotes:
915
+ raise ValueError("Git config section names must not contain an unterminated quote")
900
916
 
901
917
  @needs_values
902
918
  @set_dirty_and_flush_changes
@@ -9,7 +9,7 @@ import enum
9
9
  import re
10
10
  import warnings
11
11
 
12
- from git.cmd import handle_process_output
12
+ from git.cmd import Git, handle_process_output
13
13
  from git.compat import defenc
14
14
  from git.objects.blob import Blob
15
15
  from git.objects.util import mode_str_to_int
@@ -35,7 +35,6 @@ from git.types import PathLike, Literal
35
35
  if TYPE_CHECKING:
36
36
  from subprocess import Popen
37
37
 
38
- from git.cmd import Git
39
38
  from git.objects.base import IndexObject
40
39
  from git.objects.commit import Commit
41
40
  from git.objects.tree import Tree
@@ -190,6 +189,7 @@ class Diffable:
190
189
  other: Union[DiffConstants, "Tree", "Commit", str, None] = INDEX,
191
190
  paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
192
191
  create_patch: bool = False,
192
+ allow_unsafe_options: bool = False,
193
193
  **kwargs: Any,
194
194
  ) -> "DiffIndex[Diff]":
195
195
  """Create diffs between two items being trees, trees and index or an index and
@@ -219,6 +219,10 @@ class Diffable:
219
219
  applied makes the self to other. Patches are somewhat costly as blobs have
220
220
  to be read and diffed.
221
221
 
222
+ :param allow_unsafe_options:
223
+ If ``True``, allow options such as ``--output`` that can write to arbitrary
224
+ filesystem paths.
225
+
222
226
  :param kwargs:
223
227
  Additional arguments passed to :manpage:`git-diff(1)`, such as ``R=True`` to
224
228
  swap both sides of the diff.
@@ -231,6 +235,12 @@ class Diffable:
231
235
  an instance of :class:`~git.objects.tree.Tree` or
232
236
  :class:`~git.objects.commit.Commit`, or a git command error will occur.
233
237
  """
238
+ if not allow_unsafe_options:
239
+ Git.check_unsafe_options(
240
+ options=Git._option_candidates([other], kwargs),
241
+ unsafe_options=self.repo.unsafe_git_revision_options,
242
+ )
243
+
234
244
  args: List[Union[PathLike, Diffable]] = []
235
245
  args.append("--abbrev=40") # We need full shas.
236
246
  args.append("--full-index") # Get full index paths, not only filenames.
@@ -23,6 +23,7 @@ from gitdb.base import IStream
23
23
  from gitdb.db import MemoryDB
24
24
 
25
25
  from git.compat import defenc, force_bytes
26
+ from git.cmd import Git
26
27
  import git.diff as git_diff
27
28
  from git.exc import CheckoutError, GitCommandError, GitError, InvalidGitRepositoryError
28
29
  from git.objects import Blob, Commit, Object, Submodule, Tree
@@ -1492,6 +1493,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1492
1493
  ] = git_diff.INDEX,
1493
1494
  paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
1494
1495
  create_patch: bool = False,
1496
+ allow_unsafe_options: bool = False,
1495
1497
  **kwargs: Any,
1496
1498
  ) -> git_diff.DiffIndex[git_diff.Diff]:
1497
1499
  """Diff this index against the working copy or a :class:`~git.objects.tree.Tree`
@@ -1504,6 +1506,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1504
1506
  Will only work with indices that represent the default git index as they
1505
1507
  have not been initialized with a stream.
1506
1508
  """
1509
+ if not allow_unsafe_options:
1510
+ Git.check_unsafe_options(
1511
+ options=Git._option_candidates([other], kwargs),
1512
+ unsafe_options=self.repo.unsafe_git_revision_options,
1513
+ )
1514
+
1507
1515
  # Only run if we are the default repository index.
1508
1516
  if self._file_path != self._index_path():
1509
1517
  raise AssertionError("Cannot call %r on indices that do not represent the default git index" % self.diff())
@@ -1560,7 +1568,13 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1560
1568
  # Invert the existing R flag.
1561
1569
  cur_val = kwargs.get("R", False)
1562
1570
  kwargs["R"] = not cur_val
1563
- return other.diff(self.INDEX, paths, create_patch, **kwargs)
1571
+ return other.diff(
1572
+ self.INDEX,
1573
+ paths,
1574
+ create_patch,
1575
+ allow_unsafe_options=allow_unsafe_options,
1576
+ **kwargs,
1577
+ )
1564
1578
  # END diff against other item handling
1565
1579
 
1566
1580
  # If other is not None here, something is wrong.
@@ -1568,4 +1582,4 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1568
1582
  raise ValueError("other must be None, Diffable.INDEX, a Tree or Commit, was %r" % other)
1569
1583
 
1570
1584
  # Diff against working copy - can be handled by superclass natively.
1571
- return super().diff(other, paths, create_patch, **kwargs)
1585
+ return super().diff(other, paths, create_patch, allow_unsafe_options=allow_unsafe_options, **kwargs)
@@ -64,6 +64,17 @@ def hook_path(name: str, git_dir: PathLike) -> str:
64
64
  return osp.join(git_dir, "hooks", name)
65
65
 
66
66
 
67
+ def _commit_hook_path(name: str, index: "IndexFile") -> str:
68
+ """:return: path to the named commit hook, respecting Git's core.hooksPath."""
69
+ with index.repo.config_reader() as config:
70
+ hooks_dir = config.get("core", "hooksPath", fallback="")
71
+
72
+ if not hooks_dir:
73
+ return hook_path(name, index.repo.git_dir)
74
+
75
+ return osp.abspath(osp.join(index.repo.working_dir, osp.expanduser(hooks_dir), name))
76
+
77
+
67
78
  def _has_file_extension(path: str) -> str:
68
79
  return osp.splitext(path)[1]
69
80
 
@@ -82,7 +93,7 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
82
93
 
83
94
  :raise git.exc.HookExecutionError:
84
95
  """
85
- hp = hook_path(name, index.repo.git_dir)
96
+ hp = _commit_hook_path(name, index)
86
97
  if not os.access(hp, os.X_OK):
87
98
  return
88
99
 
@@ -94,8 +105,14 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
94
105
  if sys.platform == "win32" and not _has_file_extension(hp):
95
106
  # Windows only uses extensions to determine how to open files
96
107
  # (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]
108
+ try:
109
+ bash_hp = osp.relpath(hp, index.repo.working_dir)
110
+ except ValueError:
111
+ # Different drives have no relative path on Windows. Git Bash accepts
112
+ # an absolute path in this form, although a relative path is preferable
113
+ # because it also works with the Windows Subsystem for Linux wrapper.
114
+ bash_hp = hp
115
+ cmd = ["bash.exe", Path(bash_hp).as_posix()]
99
116
 
100
117
  process = safer_popen(
101
118
  cmd + list(args),
@@ -49,6 +49,7 @@ from typing import (
49
49
  Callable,
50
50
  Dict,
51
51
  Iterator,
52
+ List,
52
53
  Mapping,
53
54
  Sequence,
54
55
  TYPE_CHECKING,
@@ -738,122 +739,132 @@ class Submodule(IndexObject, TraversableIterableObj):
738
739
  mrepo = None
739
740
  # END init mrepo
740
741
 
742
+ def fetch_remotes(module_repo: "Repo") -> None:
743
+ rmts = module_repo.remotes
744
+ len_rmts = len(rmts)
745
+ for i, remote in enumerate(rmts):
746
+ op = FETCH
747
+ if i == 0:
748
+ op |= BEGIN
749
+ # END handle start
750
+
751
+ progress.update(
752
+ op,
753
+ i,
754
+ len_rmts,
755
+ prefix + "Fetching remote %s of submodule %r" % (remote, self.name),
756
+ )
757
+ # ===============================
758
+ if not dry_run:
759
+ remote.fetch(progress=progress)
760
+ # END handle dry-run
761
+ # ===============================
762
+ if i == len_rmts - 1:
763
+ op |= END
764
+ # END handle end
765
+ progress.update(
766
+ op,
767
+ i,
768
+ len_rmts,
769
+ prefix + "Done fetching remote of submodule %r" % self.name,
770
+ )
771
+ # END fetch new data
772
+
741
773
  try:
742
774
  # ENSURE REPO IS PRESENT AND UP-TO-DATE
743
775
  #######################################
744
776
  try:
745
777
  mrepo = self.module()
746
- rmts = mrepo.remotes
747
- len_rmts = len(rmts)
748
- for i, remote in enumerate(rmts):
749
- op = FETCH
750
- if i == 0:
751
- op |= BEGIN
752
- # END handle start
753
-
754
- progress.update(
755
- op,
756
- i,
757
- len_rmts,
758
- prefix + "Fetching remote %s of submodule %r" % (remote, self.name),
759
- )
760
- # ===============================
761
- if not dry_run:
762
- remote.fetch(progress=progress)
763
- # END handle dry-run
764
- # ===============================
765
- if i == len_rmts - 1:
766
- op |= END
767
- # END handle end
768
- progress.update(
769
- op,
770
- i,
771
- len_rmts,
772
- prefix + "Done fetching remote of submodule %r" % self.name,
773
- )
774
- # END fetch new data
778
+ fetch_remotes(mrepo)
775
779
  except InvalidGitRepositoryError:
776
780
  mrepo = None
777
781
  if not init:
778
782
  return self
779
783
  # END early abort if init is not allowed
780
784
 
781
- # There is no git-repository yet - but delete empty paths.
782
785
  checkout_module_abspath = self.abspath
783
- if not dry_run and osp.isdir(checkout_module_abspath):
786
+ module_abspath = self._module_abspath(self.repo, self.path, self.name)
787
+
788
+ # ``git submodule deinit`` leaves the repository in
789
+ # ``.git/modules`` and empties the checkout. Reconnect that retained
790
+ # repository instead of trying to clone over it.
791
+ if not dry_run and osp.isdir(module_abspath):
784
792
  try:
785
- os.rmdir(checkout_module_abspath)
786
- except OSError as e:
787
- raise OSError(
788
- "Module directory at %r does already exist and is non-empty" % checkout_module_abspath
789
- ) from e
790
- # END handle OSError
791
- # END handle directory removal
792
-
793
- # Don't check it out at first - nonetheless it will create a local
794
- # branch according to the remote-HEAD if possible.
795
- progress.update(
796
- BEGIN | CLONE,
797
- 0,
798
- 1,
799
- prefix
800
- + "Cloning url '%s' to '%s' in submodule %r" % (self.url, checkout_module_abspath, self.name),
801
- )
802
- if not dry_run:
803
- if self.url.startswith("."):
804
- url = urllib.parse.urljoin(self.repo.remotes.origin.url + "/", self.url)
793
+ git.Repo(module_abspath)
794
+ except InvalidGitRepositoryError:
795
+ pass
805
796
  else:
806
- url = self.url
807
- mrepo = self._clone_repo(
808
- self.repo,
809
- url,
810
- self.path,
811
- self.name,
812
- n=True,
813
- env=env,
814
- multi_options=clone_multi_options,
815
- allow_unsafe_options=allow_unsafe_options,
816
- allow_unsafe_protocols=allow_unsafe_protocols,
797
+ if osp.lexists(checkout_module_abspath) and (
798
+ osp.islink(checkout_module_abspath)
799
+ or not osp.isdir(checkout_module_abspath)
800
+ or os.listdir(checkout_module_abspath)
801
+ ):
802
+ raise OSError(
803
+ "Module directory at %r does already exist and is non-empty" % checkout_module_abspath
804
+ )
805
+ os.makedirs(checkout_module_abspath, exist_ok=True)
806
+ self._write_git_file_and_module_config(checkout_module_abspath, module_abspath)
807
+ mrepo = git.Repo(checkout_module_abspath)
808
+ mrepo.head.reset(mrepo.head.commit, index=True, working_tree=True)
809
+ fetch_remotes(mrepo)
810
+ with self.repo.config_writer() as writer:
811
+ writer.set_value(sm_section(self.name), "url", self.url)
812
+
813
+ if mrepo is None:
814
+ # There is no git-repository yet - but delete empty paths.
815
+ if not dry_run and osp.isdir(checkout_module_abspath):
816
+ try:
817
+ os.rmdir(checkout_module_abspath)
818
+ except OSError as e:
819
+ raise OSError(
820
+ "Module directory at %r does already exist and is non-empty" % checkout_module_abspath
821
+ ) from e
822
+ # END handle directory removal
823
+
824
+ # Don't check it out at first - nonetheless it will create a local
825
+ # branch according to the remote-HEAD if possible.
826
+ progress.update(
827
+ BEGIN | CLONE,
828
+ 0,
829
+ 1,
830
+ prefix
831
+ + "Cloning url '%s' to '%s' in submodule %r" % (self.url, checkout_module_abspath, self.name),
817
832
  )
818
- # END handle dry-run
819
- progress.update(
820
- END | CLONE,
821
- 0,
822
- 1,
823
- prefix + "Done cloning to %s" % checkout_module_abspath,
824
- )
825
-
826
- if not dry_run:
827
- # See whether we have a valid branch to check out.
828
- try:
829
- mrepo = cast("Repo", mrepo)
830
- # Find a remote which has our branch - we try to be flexible.
831
- remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name)
832
- local_branch = mkhead(mrepo, self.branch_path)
833
-
834
- # Have a valid branch, but no checkout - make sure we can figure
835
- # that out by marking the commit with a null_sha.
836
- local_branch.set_object(Object(mrepo, self.NULL_BIN_SHA))
837
- # END initial checkout + branch creation
838
-
839
- # Make sure HEAD is not detached.
840
- mrepo.head.set_reference(
841
- local_branch,
842
- logmsg="submodule: attaching head to %s" % local_branch,
833
+ if not dry_run:
834
+ if self.url.startswith("."):
835
+ url = urllib.parse.urljoin(self.repo.remotes.origin.url + "/", self.url)
836
+ else:
837
+ url = self.url
838
+ mrepo = self._clone_repo(
839
+ self.repo,
840
+ url,
841
+ self.path,
842
+ self.name,
843
+ n=True,
844
+ env=env,
845
+ multi_options=clone_multi_options,
846
+ allow_unsafe_options=allow_unsafe_options,
847
+ allow_unsafe_protocols=allow_unsafe_protocols,
843
848
  )
844
- mrepo.head.reference.set_tracking_branch(remote_branch)
845
- except (IndexError, InvalidGitRepositoryError):
846
- _logger.warning("Failed to checkout tracking branch %s", self.branch_path)
847
- # END handle tracking branch
848
-
849
- # NOTE: Have to write the repo config file as well, otherwise the
850
- # default implementation will be offended and not update the
851
- # repository. Maybe this is a good way to ensure it doesn't get into
852
- # our way, but we want to stay backwards compatible too... It's so
853
- # redundant!
854
- with self.repo.config_writer() as writer:
855
- writer.set_value(sm_section(self.name), "url", self.url)
856
- # END handle dry_run
849
+ progress.update(END | CLONE, 0, 1, prefix + "Done cloning to %s" % checkout_module_abspath)
850
+
851
+ if not dry_run:
852
+ # See whether we have a valid branch to check out.
853
+ try:
854
+ mrepo = cast("Repo", mrepo)
855
+ remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name)
856
+ local_branch = mkhead(mrepo, self.branch_path)
857
+ local_branch.set_object(Object(mrepo, self.NULL_BIN_SHA))
858
+ mrepo.head.set_reference(
859
+ local_branch,
860
+ logmsg="submodule: attaching head to %s" % local_branch,
861
+ )
862
+ mrepo.head.reference.set_tracking_branch(remote_branch)
863
+ except (IndexError, InvalidGitRepositoryError):
864
+ _logger.warning("Failed to checkout tracking branch %s", self.branch_path)
865
+
866
+ with self.repo.config_writer() as writer:
867
+ writer.set_value(sm_section(self.name), "url", self.url)
857
868
  # END handle initialization
858
869
 
859
870
  # DETERMINE SHAS TO CHECK OUT
@@ -1267,6 +1278,36 @@ class Submodule(IndexObject, TraversableIterableObj):
1267
1278
 
1268
1279
  return self
1269
1280
 
1281
+ @unbare_repo
1282
+ def deinit(self, force: bool = False) -> "Submodule":
1283
+ """Run ``git submodule deinit`` on this submodule.
1284
+
1285
+ This is a thin wrapper around ``git submodule deinit <path>``,
1286
+ which unregisters the submodule (removes its entry from
1287
+ ``.git/config`` and empties the working-tree directory)
1288
+ without deleting the submodule from ``.gitmodules``
1289
+ or its checked-out repository under ``.git/modules/``.
1290
+ A subsequent :meth:`update` will re-initialize the
1291
+ submodule from the retained contents.
1292
+
1293
+ :param force:
1294
+ If ``True``, pass ``--force`` to ``git submodule deinit``. This
1295
+ allows deinitialization even when the submodule's working tree has
1296
+ local modifications that would otherwise block the command.
1297
+
1298
+ :return:
1299
+ self
1300
+
1301
+ :note:
1302
+ Doesn't work in bare repositories.
1303
+ """
1304
+ args: List[str] = []
1305
+ if force:
1306
+ args.append("--force")
1307
+ args.extend(["--", str(self.path)])
1308
+ self.repo.git.submodule("deinit", *args)
1309
+ return self
1310
+
1270
1311
  def set_parent_commit(self, commit: Union[Commit_ish, str, None], check: bool = True) -> "Submodule":
1271
1312
  """Set this instance to use the given commit whose tree is supposed to
1272
1313
  contain the ``.gitmodules`` blob.
@@ -149,6 +149,8 @@ class Repo:
149
149
  # Can override configuration variables that execute arbitrary commands:
150
150
  "--config",
151
151
  "-c",
152
+ # Can install hooks that execute during clone:
153
+ "--template",
152
154
  ]
153
155
  """Options to :manpage:`git-clone(1)` that allow arbitrary commands to be executed.
154
156
 
@@ -159,6 +161,9 @@ class Repo:
159
161
  The ``--config``/``-c`` option allows users to override configuration variables like
160
162
  ``protocol.allow`` and ``core.gitProxy`` to execute arbitrary commands:
161
163
  https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---configltkeygtltvaluegt
164
+
165
+ The ``--template`` option can install hooks that execute during clone:
166
+ https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---templatetemplate-directory
162
167
  """
163
168
 
164
169
  unsafe_git_archive_options = [
@@ -217,7 +217,7 @@ def rmtree(path: PathLike) -> None:
217
217
  couldn't be deleted are read-only. Windows will not remove them in that case.
218
218
  """
219
219
 
220
- def handler(function: Callable, path: PathLike, _excinfo: Any) -> None:
220
+ def handler(function: Callable[[str], Any], path: str, _excinfo: Any) -> None:
221
221
  """Callback for :func:`shutil.rmtree`.
222
222
 
223
223
  This works as either a ``onexc`` or ``onerror`` style callback.
@@ -401,7 +401,7 @@ def _cygexpath(drive: Optional[str], path: str, expand_vars: bool = True) -> str
401
401
  return p_str.replace("\\", "/")
402
402
 
403
403
 
404
- _cygpath_parsers: Tuple[Tuple[Pattern[str], Callable, bool], ...] = (
404
+ _cygpath_parsers: Tuple[Tuple[Pattern[str], Callable[..., str], bool], ...] = (
405
405
  # See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
406
406
  # and: https://www.cygwin.com/cygwin-ug-net/using.html#unc-paths
407
407
  (
@@ -508,7 +508,7 @@ def get_user_id() -> str:
508
508
  return "%s@%s" % (getpass.getuser(), platform.node())
509
509
 
510
510
 
511
- def finalize_process(proc: Union[subprocess.Popen, "Git.AutoInterrupt"], **kwargs: Any) -> None:
511
+ def finalize_process(proc: Union["subprocess.Popen[Any]", "Git.AutoInterrupt"], **kwargs: Any) -> None:
512
512
  """Wait for the process (clone, fetch, pull or push) and handle its errors
513
513
  accordingly."""
514
514
  # TODO: No close proc-streams??
@@ -520,19 +520,21 @@ def expand_path(p: None, expand_vars: bool = ...) -> None: ...
520
520
 
521
521
 
522
522
  @overload
523
- def expand_path(p: PathLike, expand_vars: bool = ...) -> str:
523
+ def expand_path(p: PathLike, expand_vars: bool = ...) -> Optional[PathLike]:
524
524
  # TODO: Support for Python 3.5 has been dropped, so these overloads can be improved.
525
525
  ...
526
526
 
527
527
 
528
528
  def expand_path(p: Union[None, PathLike], expand_vars: bool = True) -> Optional[PathLike]:
529
- if isinstance(p, Path):
530
- return p.resolve()
529
+ if p is None:
530
+ return None
531
531
  try:
532
- p = osp.expanduser(p) # type: ignore[arg-type]
532
+ if isinstance(p, Path):
533
+ return p.resolve()
534
+ expanded_path = osp.expanduser(os.fspath(p))
533
535
  if expand_vars:
534
- p = osp.expandvars(p)
535
- return osp.normpath(osp.abspath(p))
536
+ expanded_path = osp.expandvars(expanded_path)
537
+ return osp.normpath(osp.abspath(expanded_path))
536
538
  except Exception:
537
539
  return None
538
540
 
@@ -767,7 +769,7 @@ class CallableRemoteProgress(RemoteProgress):
767
769
 
768
770
  __slots__ = ("_callable",)
769
771
 
770
- def __init__(self, fn: Callable) -> None:
772
+ def __init__(self, fn: Callable[..., Any]) -> None:
771
773
  self._callable = fn
772
774
  super().__init__()
773
775
 
@@ -846,7 +848,7 @@ class Actor:
846
848
  cls,
847
849
  env_name: str,
848
850
  env_email: str,
849
- config_reader: Union[None, "GitConfigParser", "SectionConstraint"] = None,
851
+ config_reader: Union[None, "GitConfigParser", "SectionConstraint[GitConfigParser]"] = None,
850
852
  ) -> "Actor":
851
853
  actor = Actor("", "")
852
854
  user_id = None # We use this to avoid multiple calls to getpass.getuser().
@@ -882,7 +884,9 @@ class Actor:
882
884
  return actor
883
885
 
884
886
  @classmethod
885
- def committer(cls, config_reader: Union[None, "GitConfigParser", "SectionConstraint"] = None) -> "Actor":
887
+ def committer(
888
+ cls, config_reader: Union[None, "GitConfigParser", "SectionConstraint[GitConfigParser]"] = None
889
+ ) -> "Actor":
886
890
  """
887
891
  :return:
888
892
  :class:`Actor` instance corresponding to the configured committer. It
@@ -897,7 +901,9 @@ class Actor:
897
901
  return cls._main_actor(cls.env_committer_name, cls.env_committer_email, config_reader)
898
902
 
899
903
  @classmethod
900
- def author(cls, config_reader: Union[None, "GitConfigParser", "SectionConstraint"] = None) -> "Actor":
904
+ def author(
905
+ cls, config_reader: Union[None, "GitConfigParser", "SectionConstraint[GitConfigParser]"] = None
906
+ ) -> "Actor":
901
907
  """Same as :meth:`committer`, but defines the main author. It may be specified
902
908
  in the environment, but defaults to the committer."""
903
909
  return cls._main_actor(cls.env_author_name, cls.env_author_email, config_reader)
@@ -980,11 +986,11 @@ class IndexFileSHA1Writer:
980
986
 
981
987
  __slots__ = ("f", "sha1")
982
988
 
983
- def __init__(self, f: IO) -> None:
989
+ def __init__(self, f: IO[bytes]) -> None:
984
990
  self.f = f
985
991
  self.sha1 = make_sha(b"")
986
992
 
987
- def write(self, data: AnyStr) -> int:
993
+ def write(self, data: bytes) -> int:
988
994
  self.sha1.update(data)
989
995
  return self.f.write(data)
990
996
 
@@ -1181,6 +1187,7 @@ class IterableList(List[T_IterableObj]): # type: ignore[type-var]
1181
1187
  return super().__new__(cls)
1182
1188
 
1183
1189
  def __init__(self, id_attr: str, prefix: str = "") -> None:
1190
+ super().__init__()
1184
1191
  self._id_attr = id_attr
1185
1192
  self._prefix = prefix
1186
1193
 
@@ -1210,7 +1217,9 @@ class IterableList(List[T_IterableObj]): # type: ignore[type-var]
1210
1217
  # END for each item
1211
1218
  return list.__getattribute__(self, attr)
1212
1219
 
1213
- def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> T_IterableObj: # type: ignore[override]
1220
+ def __getitem__( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
1221
+ self, index: Union[SupportsIndex, int, slice, str]
1222
+ ) -> T_IterableObj:
1214
1223
  if isinstance(index, int):
1215
1224
  return list.__getitem__(self, index)
1216
1225
  elif isinstance(index, slice):
@@ -1288,7 +1297,7 @@ class IterableObj(Protocol):
1288
1297
  :return:
1289
1298
  list(Item,...) list of item instances
1290
1299
  """
1291
- out_list: IterableList = IterableList(cls._id_attribute_)
1300
+ out_list: IterableList[T_IterableObj] = IterableList(cls._id_attribute_)
1292
1301
  out_list.extend(cls.iter_items(repo, *args, **kwargs))
1293
1302
  return out_list
1294
1303
 
@@ -1297,7 +1306,8 @@ class IterableClassWatcher(type):
1297
1306
  """Metaclass that issues :exc:`DeprecationWarning` when :class:`git.util.Iterable`
1298
1307
  is subclassed."""
1299
1308
 
1300
- def __init__(cls, name: str, bases: Tuple, clsdict: Dict) -> None:
1309
+ def __init__(cls, name: str, bases: Tuple[type, ...], clsdict: Dict[str, Any]) -> None:
1310
+ super().__init__(name, bases, clsdict)
1301
1311
  for base in bases:
1302
1312
  if type(base) is IterableClassWatcher:
1303
1313
  warnings.warn(
@@ -33,6 +33,17 @@ exclude = ["^git/ext/gitdb"]
33
33
  module = "gitdb.*"
34
34
  ignore_missing_imports = true
35
35
 
36
+ [tool.basedpyright]
37
+ typeCheckingMode = "standard"
38
+ pythonVersion = "3.7"
39
+ extraPaths = [
40
+ "git/ext/gitdb",
41
+ "git/ext/gitdb/gitdb/ext/smmap",
42
+ ]
43
+ exclude = [
44
+ "git/ext/gitdb",
45
+ ]
46
+
36
47
  [tool.coverage.run]
37
48
  source = ["git"]
38
49
 
gitpython-3.1.52/VERSION DELETED
@@ -1 +0,0 @@
1
- 3.1.52
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes