GitPython 3.1.46__py3-none-any.whl → 3.1.48__py3-none-any.whl

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.
git/__init__.py CHANGED
@@ -86,7 +86,7 @@ __all__ = [
86
86
  "to_hex_sha",
87
87
  ]
88
88
 
89
- __version__ = '3.1.46'
89
+ __version__ = '3.1.48'
90
90
 
91
91
  from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Tuple, Union
92
92
 
git/cmd.py CHANGED
@@ -368,8 +368,12 @@ class _AutoInterrupt:
368
368
  status = proc.wait() # Ensure the process goes away.
369
369
 
370
370
  self.status = self._status_code_if_terminate or status
371
- except OSError as ex:
372
- _logger.info("Ignored error after process had died: %r", ex)
371
+ except (OSError, AttributeError) as ex:
372
+ # On interpreter shutdown (notably on Windows), parts of the stdlib used by
373
+ # subprocess can already be torn down (e.g. `subprocess._winapi` becomes None),
374
+ # which can cause AttributeError during terminate(). In that case, we prefer
375
+ # to silently ignore to avoid noisy "Exception ignored in: __del__" messages.
376
+ _logger.info("Ignored error while terminating process: %r", ex)
373
377
  # END exception handling
374
378
 
375
379
  def __del__(self) -> None:
@@ -940,6 +944,21 @@ class Git(metaclass=_GitMeta):
940
944
  f"The `{protocol}::` protocol looks suspicious, use `allow_unsafe_protocols=True` to allow it."
941
945
  )
942
946
 
947
+ @classmethod
948
+ def _canonicalize_option_name(cls, option: str) -> str:
949
+ """Return the option name used for unsafe-option checks.
950
+
951
+ Examples:
952
+ ``"--upload-pack=/tmp/helper"`` -> ``"upload-pack"``
953
+ ``"upload_pack"`` -> ``"upload-pack"``
954
+ ``"--config core.filemode=false"`` -> ``"config"``
955
+ """
956
+ option_name = option.lstrip("-").split("=", 1)[0]
957
+ option_tokens = option_name.split(None, 1)
958
+ if not option_tokens:
959
+ return ""
960
+ return dashify(option_tokens[0])
961
+
943
962
  @classmethod
944
963
  def check_unsafe_options(cls, options: List[str], unsafe_options: List[str]) -> None:
945
964
  """Check for unsafe options.
@@ -947,15 +966,12 @@ class Git(metaclass=_GitMeta):
947
966
  Some options that are passed to ``git <command>`` can be used to execute
948
967
  arbitrary commands. These are blocked by default.
949
968
  """
950
- # Options can be of the form `foo`, `--foo bar`, or `--foo=bar`, so we need to
951
- # check if they start with "--foo" or if they are equal to "foo".
952
- bare_unsafe_options = [option.lstrip("-") for option in unsafe_options]
969
+ # Options can be of the form `foo`, `--foo`, `--foo bar`, or `--foo=bar`.
970
+ canonical_unsafe_options = {cls._canonicalize_option_name(option): option for option in unsafe_options}
953
971
  for option in options:
954
- for unsafe_option, bare_option in zip(unsafe_options, bare_unsafe_options):
955
- if option.startswith(unsafe_option) or option == bare_option:
956
- raise UnsafeOptionError(
957
- f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it."
958
- )
972
+ unsafe_option = canonical_unsafe_options.get(cls._canonicalize_option_name(option))
973
+ if unsafe_option is not None:
974
+ raise UnsafeOptionError(f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it.")
959
975
 
960
976
  AutoInterrupt: TypeAlias = _AutoInterrupt
961
977
 
@@ -1360,25 +1376,29 @@ class Git(metaclass=_GitMeta):
1360
1376
  if output_stream is None:
1361
1377
  stdout_value, stderr_value = communicate()
1362
1378
  # Strip trailing "\n".
1363
- if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
1379
+ if stdout_value is not None and stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
1364
1380
  stdout_value = stdout_value[:-1]
1365
- if stderr_value.endswith(newline): # type: ignore[arg-type]
1381
+ if stderr_value is not None and stderr_value.endswith(newline): # type: ignore[arg-type]
1366
1382
  stderr_value = stderr_value[:-1]
1367
1383
 
1368
1384
  status = proc.returncode
1369
1385
  else:
1370
1386
  max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
1371
- stream_copy(proc.stdout, output_stream, max_chunk_size)
1372
- stdout_value = proc.stdout.read()
1373
- stderr_value = proc.stderr.read()
1387
+ if proc.stdout is not None:
1388
+ stream_copy(proc.stdout, output_stream, max_chunk_size)
1389
+ stdout_value = proc.stdout.read()
1390
+ if proc.stderr is not None:
1391
+ stderr_value = proc.stderr.read()
1374
1392
  # Strip trailing "\n".
1375
- if stderr_value.endswith(newline): # type: ignore[arg-type]
1393
+ if stderr_value is not None and stderr_value.endswith(newline): # type: ignore[arg-type]
1376
1394
  stderr_value = stderr_value[:-1]
1377
1395
  status = proc.wait()
1378
1396
  # END stdout handling
1379
1397
  finally:
1380
- proc.stdout.close()
1381
- proc.stderr.close()
1398
+ if proc.stdout is not None:
1399
+ proc.stdout.close()
1400
+ if proc.stderr is not None:
1401
+ proc.stderr.close()
1382
1402
 
1383
1403
  if self.GIT_PYTHON_TRACE == "full":
1384
1404
  cmdstr = " ".join(redacted_command)
@@ -1568,7 +1588,7 @@ class Git(metaclass=_GitMeta):
1568
1588
 
1569
1589
  turns into::
1570
1590
 
1571
- git rev-list max-count 10 --header master
1591
+ git rev-list --max-count=10 --header=master
1572
1592
 
1573
1593
  :return:
1574
1594
  Same as :meth:`execute`. If no args are given, used :meth:`execute`'s
git/config.py CHANGED
@@ -549,11 +549,21 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
549
549
  :return:
550
550
  The list of paths, where each path is a tuple of (option, value).
551
551
  """
552
+
553
+ def _all_items(section: str) -> List[Tuple[str, str]]:
554
+ """Return all (key, value) pairs for a section, including duplicate keys."""
555
+ return [
556
+ (key, value)
557
+ for key, values in self._sections[section].items_all()
558
+ if key != "__name__"
559
+ for value in values
560
+ ]
561
+
552
562
  paths = []
553
563
 
554
564
  for section in self.sections():
555
565
  if section == "include":
556
- paths += self.items(section)
566
+ paths += _all_items(section)
557
567
 
558
568
  match = CONDITIONAL_INCLUDE_REGEXP.search(section)
559
569
  if match is None or self._repo is None:
@@ -579,7 +589,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
579
589
  )
580
590
  if self._repo.git_dir:
581
591
  if fnmatch.fnmatchcase(os.fspath(self._repo.git_dir), value):
582
- paths += self.items(section)
592
+ paths += _all_items(section)
583
593
 
584
594
  elif keyword == "onbranch":
585
595
  try:
@@ -589,11 +599,11 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
589
599
  continue
590
600
 
591
601
  if fnmatch.fnmatchcase(branch_name, value):
592
- paths += self.items(section)
602
+ paths += _all_items(section)
593
603
  elif keyword == "hasconfig:remote.*.url":
594
604
  for remote in self._repo.remotes:
595
605
  if fnmatch.fnmatchcase(remote.url, value):
596
- paths += self.items(section)
606
+ paths += _all_items(section)
597
607
  break
598
608
  return paths
599
609
 
git/index/base.py CHANGED
@@ -1133,6 +1133,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1133
1133
  author_date: Union[datetime.datetime, str, None] = None,
1134
1134
  commit_date: Union[datetime.datetime, str, None] = None,
1135
1135
  skip_hooks: bool = False,
1136
+ trailers: Union[None, "Dict[str, str]", "List[Tuple[str, str]]"] = None,
1136
1137
  ) -> Commit:
1137
1138
  """Commit the current default index file, creating a
1138
1139
  :class:`~git.objects.commit.Commit` object.
@@ -1169,6 +1170,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1169
1170
  committer=committer,
1170
1171
  author_date=author_date,
1171
1172
  commit_date=commit_date,
1173
+ trailers=trailers,
1172
1174
  )
1173
1175
  if not skip_hooks:
1174
1176
  run_commit_hook("post-commit", self)
git/objects/commit.py CHANGED
@@ -450,14 +450,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
450
450
  :return:
451
451
  List containing key-value tuples of whitespace stripped trailer information.
452
452
  """
453
- cmd = ["git", "interpret-trailers", "--parse"]
454
- proc: Git.AutoInterrupt = self.repo.git.execute( # type: ignore[call-overload]
455
- cmd,
456
- as_process=True,
457
- istream=PIPE,
458
- )
459
- trailer: str = proc.communicate(str(self.message).encode())[0].decode("utf8")
460
- trailer = trailer.strip()
453
+ trailer = self._interpret_trailers(self.repo, self.message, ["--parse"], encoding=self.encoding).strip()
461
454
 
462
455
  if not trailer:
463
456
  return []
@@ -469,6 +462,27 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
469
462
 
470
463
  return trailer_list
471
464
 
465
+ @classmethod
466
+ def _interpret_trailers(
467
+ cls,
468
+ repo: "Repo",
469
+ message: Union[str, bytes],
470
+ trailer_args: Sequence[str],
471
+ encoding: str = default_encoding,
472
+ ) -> str:
473
+ message_bytes = message if isinstance(message, bytes) else message.encode(encoding, errors="strict")
474
+ cmd = [repo.git.GIT_PYTHON_GIT_EXECUTABLE, "interpret-trailers", *trailer_args]
475
+ proc: Git.AutoInterrupt = repo.git.execute( # type: ignore[call-overload]
476
+ cmd,
477
+ as_process=True,
478
+ istream=PIPE,
479
+ )
480
+ try:
481
+ stdout_bytes, _ = proc.communicate(message_bytes)
482
+ return stdout_bytes.decode(encoding, errors="strict")
483
+ finally:
484
+ finalize_process(proc)
485
+
472
486
  @property
473
487
  def trailers_dict(self) -> Dict[str, List[str]]:
474
488
  """Get the trailers of the message as a dictionary.
@@ -570,6 +584,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
570
584
  committer: Union[None, Actor] = None,
571
585
  author_date: Union[None, str, datetime.datetime] = None,
572
586
  commit_date: Union[None, str, datetime.datetime] = None,
587
+ trailers: Union[None, Dict[str, str], List[Tuple[str, str]]] = None,
573
588
  ) -> "Commit":
574
589
  """Commit the given tree, creating a :class:`Commit` object.
575
590
 
@@ -609,6 +624,14 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
609
624
  :param commit_date:
610
625
  The timestamp for the committer field.
611
626
 
627
+ :param trailers:
628
+ Optional trailer key-value pairs to append to the commit message.
629
+ Can be a dictionary mapping trailer keys to values, or a list of
630
+ ``(key, value)`` tuples (useful when the same key appears multiple
631
+ times, e.g. multiple ``Signed-off-by`` trailers). Trailers are
632
+ appended using ``git interpret-trailers``.
633
+ See :manpage:`git-interpret-trailers(1)`.
634
+
612
635
  :return:
613
636
  :class:`Commit` object representing the new commit.
614
637
 
@@ -678,6 +701,21 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
678
701
  tree = repo.tree(tree)
679
702
  # END tree conversion
680
703
 
704
+ # APPLY TRAILERS
705
+ if trailers:
706
+ trailer_args: List[str] = []
707
+ if isinstance(trailers, dict):
708
+ for key, val in trailers.items():
709
+ trailer_args.append("--trailer")
710
+ trailer_args.append(f"{key}: {val}")
711
+ else:
712
+ for key, val in trailers:
713
+ trailer_args.append("--trailer")
714
+ trailer_args.append(f"{key}: {val}")
715
+
716
+ message = cls._interpret_trailers(repo, str(message), trailer_args)
717
+ # END apply trailers
718
+
681
719
  # CREATE NEW COMMIT
682
720
  new_commit = cls(
683
721
  repo,
git/refs/log.py CHANGED
@@ -4,7 +4,6 @@
4
4
  __all__ = ["RefLog", "RefLogEntry"]
5
5
 
6
6
  from mmap import mmap
7
- import os.path as osp
8
7
  import re
9
8
  import time as _time
10
9
 
@@ -212,8 +211,11 @@ class RefLog(List[RefLogEntry], Serializable):
212
211
 
213
212
  :param ref:
214
213
  :class:`~git.refs.symbolic.SymbolicReference` instance
214
+
215
+ :raise ValueError:
216
+ If `ref.path` is invalid or escapes the repository's reflog directory.
215
217
  """
216
- return osp.join(ref.repo.git_dir, "logs", to_native_path(ref.path))
218
+ return to_native_path(ref._get_validated_reflog_path(ref.repo, ref.path))
217
219
 
218
220
  @classmethod
219
221
  def iter_entries(cls, stream: Union[str, "BytesIO", mmap]) -> Iterator[RefLogEntry]:
git/refs/remote.py CHANGED
@@ -58,17 +58,20 @@ class RemoteReference(Head):
58
58
  `kwargs` are given for comparability with the base class method as we
59
59
  should not narrow the signature.
60
60
  """
61
+ for ref in refs:
62
+ cls._check_ref_name_valid(ref.path)
63
+
61
64
  repo.git.branch("-d", "-r", *refs)
62
65
  # The official deletion method will ignore remote symbolic refs - these are
63
66
  # generally ignored in the refs/ folder. We don't though and delete remainders
64
67
  # manually.
65
68
  for ref in refs:
66
69
  try:
67
- os.remove(os.path.join(repo.common_dir, ref.path))
70
+ os.remove(cls._get_validated_path(repo.common_dir, ref.path))
68
71
  except OSError:
69
72
  pass
70
73
  try:
71
- os.remove(os.path.join(repo.git_dir, ref.path))
74
+ os.remove(cls._get_validated_path(repo.git_dir, ref.path))
72
75
  except OSError:
73
76
  pass
74
77
  # END for each ref
git/refs/symbolic.py CHANGED
@@ -110,6 +110,32 @@ class SymbolicReference:
110
110
  def abspath(self) -> PathLike:
111
111
  return join_path_native(_git_dir(self.repo, self.path), self.path)
112
112
 
113
+ @staticmethod
114
+ def _get_validated_path(base: PathLike, path: PathLike) -> str:
115
+ path = os.fspath(path)
116
+ base_path = os.path.realpath(os.fspath(base))
117
+ abs_path = os.path.realpath(os.path.join(base_path, path))
118
+ try:
119
+ common_path = os.path.commonpath([base_path, abs_path])
120
+ except ValueError as e:
121
+ raise ValueError("Reference path %r escapes the repository" % path) from e
122
+ if os.path.normcase(common_path) != os.path.normcase(base_path):
123
+ raise ValueError("Reference path %r escapes the repository" % path)
124
+ return abs_path
125
+
126
+ @classmethod
127
+ def _get_validated_ref_path(cls, repo: "Repo", path: PathLike) -> str:
128
+ """Return the absolute filesystem path for a ref after validating it."""
129
+ cls._check_ref_name_valid(path)
130
+ ref_path = os.fspath(path)
131
+ return cls._get_validated_path(_git_dir(repo, ref_path), ref_path)
132
+
133
+ @classmethod
134
+ def _get_validated_reflog_path(cls, repo: "Repo", path: PathLike) -> str:
135
+ """Return the absolute filesystem path for a reflog after validating it."""
136
+ cls._check_ref_name_valid(path)
137
+ return cls._get_validated_path(os.path.join(repo.git_dir, "logs"), path)
138
+
113
139
  @classmethod
114
140
  def _get_packed_refs_path(cls, repo: "Repo") -> str:
115
141
  return os.path.join(repo.common_dir, "packed-refs")
@@ -485,7 +511,7 @@ class SymbolicReference:
485
511
  # END handle non-existing
486
512
  # END retrieve old hexsha
487
513
 
488
- fpath = self.abspath
514
+ fpath = self._get_validated_ref_path(self.repo, self.path)
489
515
  assure_directory_exists(fpath, is_file=True)
490
516
 
491
517
  lfd = LockedFD(fpath)
@@ -632,7 +658,7 @@ class SymbolicReference:
632
658
  Alternatively the symbolic reference to be deleted.
633
659
  """
634
660
  full_ref_path = cls.to_full_path(path)
635
- abs_path = os.path.join(repo.common_dir, full_ref_path)
661
+ abs_path = cls._get_validated_ref_path(repo, full_ref_path)
636
662
  if os.path.exists(abs_path):
637
663
  os.remove(abs_path)
638
664
  else:
@@ -695,9 +721,8 @@ class SymbolicReference:
695
721
  symbolic reference. Otherwise it will be resolved to the corresponding object
696
722
  and a detached symbolic reference will be created instead.
697
723
  """
698
- git_dir = _git_dir(repo, path)
699
724
  full_ref_path = cls.to_full_path(path)
700
- abs_ref_path = os.path.join(git_dir, full_ref_path)
725
+ abs_ref_path = cls._get_validated_ref_path(repo, full_ref_path)
701
726
 
702
727
  # Figure out target data.
703
728
  target = reference
@@ -789,8 +814,8 @@ class SymbolicReference:
789
814
  if self.path == new_path:
790
815
  return self
791
816
 
792
- new_abs_path = os.path.join(_git_dir(self.repo, new_path), new_path)
793
- cur_abs_path = os.path.join(_git_dir(self.repo, self.path), self.path)
817
+ new_abs_path = self._get_validated_ref_path(self.repo, new_path)
818
+ cur_abs_path = self._get_validated_ref_path(self.repo, self.path)
794
819
  if os.path.isfile(new_abs_path):
795
820
  if not force:
796
821
  # If they point to the same file, it's not an error.
git/repo/base.py CHANGED
@@ -1042,11 +1042,19 @@ class Repo:
1042
1042
  :raise TypeError:
1043
1043
  If HEAD is detached.
1044
1044
 
1045
+ :raise ValueError:
1046
+ If HEAD points to the ``.invalid`` ref Git uses to mark refs as
1047
+ incompatible with older clients.
1048
+
1045
1049
  :return:
1046
1050
  :class:`~git.refs.head.Head` to the active branch
1047
1051
  """
1048
- # reveal_type(self.head.reference) # => Reference
1049
- return self.head.reference
1052
+ active_branch = self.head.reference
1053
+ if active_branch.name == ".invalid":
1054
+ raise ValueError(
1055
+ "HEAD points to 'refs/heads/.invalid', which Git uses to mark refs as incompatible with older clients"
1056
+ )
1057
+ return active_branch
1050
1058
 
1051
1059
  def blame_incremental(self, rev: str | HEAD | None, file: str, **kwargs: Any) -> Iterator["BlameEntry"]:
1052
1060
  """Iterator for blame information for the given file at the given revision.
@@ -1378,8 +1386,8 @@ class Repo:
1378
1386
  Git.check_unsafe_protocols(url)
1379
1387
  if not allow_unsafe_options:
1380
1388
  Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=cls.unsafe_git_clone_options)
1381
- if not allow_unsafe_options and multi_options:
1382
- Git.check_unsafe_options(options=multi_options, unsafe_options=cls.unsafe_git_clone_options)
1389
+ if not allow_unsafe_options and multi:
1390
+ Git.check_unsafe_options(options=multi, unsafe_options=cls.unsafe_git_clone_options)
1383
1391
 
1384
1392
  proc = git.clone(
1385
1393
  multi,
git/util.py CHANGED
@@ -289,7 +289,7 @@ def join_path(a: PathLike, *p: PathLike) -> PathLike:
289
289
 
290
290
  if sys.platform == "win32":
291
291
 
292
- def to_native_path_windows(path: PathLike) -> PathLike:
292
+ def to_native_path_windows(path: PathLike) -> str:
293
293
  path = os.fspath(path)
294
294
  return path.replace("/", "\\")
295
295
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GitPython
3
- Version: 3.1.46
3
+ Version: 3.1.48
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
@@ -41,7 +41,7 @@ Requires-Dist: pytest-mock; extra == "test"
41
41
  Requires-Dist: pytest-sugar; extra == "test"
42
42
  Requires-Dist: typing-extensions; python_version < "3.11" and extra == "test"
43
43
  Provides-Extra: doc
44
- Requires-Dist: sphinx<7.2,>=7.1.2; extra == "doc"
44
+ Requires-Dist: sphinx<8,>=7.4.7; extra == "doc"
45
45
  Requires-Dist: sphinx_rtd_theme; extra == "doc"
46
46
  Requires-Dist: sphinx-autodoc-typehints; extra == "doc"
47
47
  Dynamic: author
@@ -105,7 +105,7 @@ by setting the `GIT_PYTHON_GIT_EXECUTABLE=<path/to/git>` environment variable.
105
105
  - Git (1.7.x or newer)
106
106
  - Python >= 3.7
107
107
 
108
- The list of dependencies are listed in `./requirements.txt` and `./test-requirements.txt`.
108
+ The list of dependencies are listed in [`./requirements.txt`](https://github.com/gitpython-developers/GitPython/blob/main/requirements.txt) and [`./test-requirements.txt`](https://github.com/gitpython-developers/GitPython/blob/main/test-requirements.txt).
109
109
  The installer takes care of installing them for you.
110
110
 
111
111
  ### INSTALL
@@ -239,7 +239,7 @@ Style and formatting checks, and running tests on all the different supported Py
239
239
 
240
240
  #### Configuration files
241
241
 
242
- Specific tools are all configured in the `./pyproject.toml` file:
242
+ Specific tools are all configured in the [`./pyproject.toml`](https://github.com/gitpython-developers/GitPython/blob/main/pyproject.toml) file:
243
243
 
244
244
  - `pytest` (test runner)
245
245
  - `coverage.py` (code coverage)
@@ -248,9 +248,9 @@ Specific tools are all configured in the `./pyproject.toml` file:
248
248
 
249
249
  Orchestration tools:
250
250
 
251
- - Configuration for `pre-commit` is in the `./.pre-commit-config.yaml` file.
252
- - Configuration for `tox` is in `./tox.ini`.
253
- - Configuration for GitHub Actions (CI) is in files inside `./.github/workflows/`.
251
+ - Configuration for `pre-commit` is in the [`./.pre-commit-config.yaml`](https://github.com/gitpython-developers/GitPython/blob/main/.pre-commit-config.yaml) file.
252
+ - Configuration for `tox` is in [`./tox.ini`](https://github.com/gitpython-developers/GitPython/blob/main/tox.ini).
253
+ - Configuration for GitHub Actions (CI) is in files inside [`./.github/workflows/`](https://github.com/gitpython-developers/GitPython/tree/main/.github/workflows).
254
254
 
255
255
  ### Contributions
256
256
 
@@ -271,8 +271,8 @@ Please have a look at the [contributions file][contributing].
271
271
 
272
272
  ### How to make a new release
273
273
 
274
- 1. Update/verify the **version** in the `VERSION` file.
275
- 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>`
274
+ 1. Update/verify the **version** in the [`VERSION`](https://github.com/gitpython-developers/GitPython/blob/main/VERSION) file.
275
+ 2. Update/verify that the [`doc/source/changes.rst`](https://github.com/gitpython-developers/GitPython/blob/main/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>`
276
276
  3. Commit everything.
277
277
  4. Run `git tag -s <version>` to tag the version in Git.
278
278
  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`.)
@@ -299,7 +299,7 @@ Please have a look at the [contributions file][contributing].
299
299
 
300
300
  [3-Clause BSD License](https://opensource.org/license/bsd-3-clause/), also known as the New BSD License. See the [LICENSE file][license].
301
301
 
302
- One file exclusively used for fuzz testing is subject to [a separate license, detailed here](./fuzzing/README.md#license).
302
+ One file exclusively used for fuzz testing is subject to [a separate license, detailed here](https://github.com/gitpython-developers/GitPython/blob/main/fuzzing/README.md#license).
303
303
  This file is not included in the wheel or sdist packages published by the maintainers of GitPython.
304
304
 
305
305
  [contributing]: https://github.com/gitpython-developers/GitPython/blob/main/CONTRIBUTING.md
@@ -1,23 +1,23 @@
1
- git/__init__.py,sha256=IFCdAVZx_IuQOh_BBdtSkdIrZHsOSd0cj0O50t9orso,8899
2
- git/cmd.py,sha256=y01yN8P2JFuFP5dGzHIW9vd-uBQjPzK8yfZMZIZZb34,67472
1
+ git/__init__.py,sha256=n7ZUyB_I0GQubucxnwYUAvFnLL3hiIF-G36z4clqUL4,8899
2
+ git/cmd.py,sha256=4Ly0NF9kt1JarTD0zpPv7a0Al5I2PaohRMIG0UGKLCw,68559
3
3
  git/compat.py,sha256=y1E6y6O2q5r8clSlr8ZNmuIWG9nmHuehQEsVsmBffs8,4526
4
- git/config.py,sha256=pMrIel6qEqbzC1MLVjoyG9MRDemiEVP-paptgnAV9sA,35505
4
+ git/config.py,sha256=vH4cb4n4HR_691Tex-ac3z-nLJpQqWxUsff1vqWZ-D8,35867
5
5
  git/db.py,sha256=vIW9uWSbqu99zbuU2ZDmOhVOv1UPTmxrnqiCtRHCfjE,2368
6
6
  git/diff.py,sha256=xhOeMmFDmXOdpje2vA1mzJpmpBnyX-nrt-mp2RiVR1M,27172
7
7
  git/exc.py,sha256=Gc7g1pHpn8OmTse30NHmJVsBJ2CYH8LxaR8y8UA3lIM,7119
8
8
  git/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  git/remote.py,sha256=pYn9dAlz-QwvNMWXD1M57pMPQitthOM86qTRK_cpTqU,46786
10
10
  git/types.py,sha256=d3oDduUGjfoqffcUL_GOD-3qroF8m3r9ai6Xi9_FraM,10279
11
- git/util.py,sha256=T1M3Nv-AbKSeZ3if33bpJFX450EeoGK67tr3SrEkjlU,44057
11
+ git/util.py,sha256=cCbGpBpnQq1MtW_REe8ApsAGTzp9JgVCMUN8KFgnx14,44052
12
12
  git/index/__init__.py,sha256=i-Nqb8Lufp9aFbmxpQBORmmQnjEVVM1Pn58fsQkyGgQ,406
13
- git/index/base.py,sha256=QBzkdpNKSjXVRDUqcl08BPyF0VItsO6uXzziclwNPJI,61121
13
+ git/index/base.py,sha256=PdOmwjSjVXdYJF31y1853QdYTV8qUVAgAK0YZw3eyEI,61233
14
14
  git/index/fun.py,sha256=v0WnKQxaAffMZxXXC7pQOiMvRRJfODhUNKDMruxKrN4,17110
15
15
  git/index/typ.py,sha256=ZjRWp5J79OwFQor1XXN4C6JmHTDclD9H-tKZtAeqyTI,7019
16
16
  git/index/util.py,sha256=f0BfE2JPkdypsB2Q_HJ8KU7MC45RRHywL_GWwxa_5hw,3676
17
17
  git/objects/__init__.py,sha256=O6ZL_olX7e5-8iIbKviRPkVSJxN37WA-EC0q9d48U5Y,637
18
18
  git/objects/base.py,sha256=5-p9uSGWvPxX9hidvkGH9tQOJoH2eaRggUbMr9VJiL0,10285
19
19
  git/objects/blob.py,sha256=G9PZMNc5CFRQFNnlquBRAM5-jZg8581lRcxkvARobSk,1231
20
- git/objects/commit.py,sha256=VhfRNpYM-ormwpXjY6cKV6Zur3GHsuDzVn4TP0mSz2I,30565
20
+ git/objects/commit.py,sha256=uD--bk2e-fNcnOEEPTutEHjx170Pt0ytJfe5zWKrFWc,32191
21
21
  git/objects/fun.py,sha256=B4jCqhAjm6Hl79GK58FPzW1H9K6Wc7Tx0rssyWmAcEE,8935
22
22
  git/objects/tag.py,sha256=jAGESnpmTEv-dLakPzheT5ILZFFArcItnXYqfxfDrgc,4441
23
23
  git/objects/tree.py,sha256=MmExcS59Iamb2cBqUvoHPdzgixabyvv5voHabossrl8,13898
@@ -28,17 +28,17 @@ git/objects/submodule/root.py,sha256=5eTtYNHasqdPq6q0oDCPr7IaO6uAHL3b4DxMoiO2LhE
28
28
  git/objects/submodule/util.py,sha256=sQqAYaiSJdFkZa9NlAuK_wTsMNiS-kkQnQjvIoJtc_o,3509
29
29
  git/refs/__init__.py,sha256=DWlJNnsx-4jM_E-VycbP-FZUdn6iWhjnH_uZ_pZXBro,509
30
30
  git/refs/head.py,sha256=59MUMVu9phmE_ImkmNw0sDDSCc75s7TxkQj_cKzRhbo,10397
31
- git/refs/log.py,sha256=ooqlw_mn14FSQGvZXDC0Tt4MQG-4Hob8T92SDH9ZzfY,12517
31
+ git/refs/log.py,sha256=X88JTqjwX1ShvHr-NHNAZj0qLiHbVoF-0v3VymZDwoM,12612
32
32
  git/refs/reference.py,sha256=uzrvNTZ5fg7A0Aq4VC3KLSMbksgYUf4KvmHau0tFYIM,5646
33
- git/refs/remote.py,sha256=WwqV9T7BbYf3F_WZNUQivu9xktIIKGklCjDpwQrhD-A,2806
34
- git/refs/symbolic.py,sha256=seRKwWw7eegQyGoObE-PuuVpSvNyQRUAXqFRq0wI6yA,34892
33
+ git/refs/remote.py,sha256=WTlkJtpu895jJxbknDNOwL31tivQY_AHQrvroUoFBX4,2902
34
+ git/refs/symbolic.py,sha256=7ScmZHwlqPDKX9bxVC5queSZXUgqEY7_J_Si6gDBSLw,36139
35
35
  git/refs/tag.py,sha256=AKrFrMhkNzX8e9x5WmDRtHovbN9zYQghuKoneVW-TJg,5002
36
36
  git/repo/__init__.py,sha256=CILSVH36fX_WxVFSjD9o1WF5LgsNedPiJvSngKZqfVU,210
37
- git/repo/base.py,sha256=WB-tKymX1pCWJS6NJvk8nrc9uwjPKOswfIjIB4Piylg,59900
37
+ git/repo/base.py,sha256=XI6FNYbd2HwrayazVPprFEHTMS6CHkzRZPjkYhw_elM,60220
38
38
  git/repo/fun.py,sha256=sjzJH8jy18nacsJjKKIqRBKadhhhxiyjmZKWXDErNnw,13787
39
- gitpython-3.1.46.dist-info/licenses/AUTHORS,sha256=_upJOkW3eLK_ZywFzQDkKDp4JtF6xBf_Qk0ObAqTPvE,2347
40
- gitpython-3.1.46.dist-info/licenses/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
41
- gitpython-3.1.46.dist-info/METADATA,sha256=2SpjTUkbQB5rdgU7bSkG9Bd27Cx-JRgdWGEMZ8MKHgs,13492
42
- gitpython-3.1.46.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
43
- gitpython-3.1.46.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
44
- gitpython-3.1.46.dist-info/RECORD,,
39
+ gitpython-3.1.48.dist-info/licenses/AUTHORS,sha256=uyjC8YZ5vcAlx2nSJ44J8gE7wjA8nc69_zWYo_HKtVc,2360
40
+ gitpython-3.1.48.dist-info/licenses/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
41
+ gitpython-3.1.48.dist-info/METADATA,sha256=qvywsw3FCGzL89bcxLt68LzVTKZRbiWdgdqbIpsPttM,14187
42
+ gitpython-3.1.48.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
43
+ gitpython-3.1.48.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
44
+ gitpython-3.1.48.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (82.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -56,5 +56,6 @@ Contributors are:
56
56
  -Ethan Lin <et.repositories _at_ gmail.com>
57
57
  -Jonas Scharpf <jonas.scharpf _at_ checkmk.com>
58
58
  -Gordon Marx
59
+ -Enji Cooper
59
60
 
60
61
  Portions derived from other open source works and are clearly marked.