GeezerVison is showing github.com — Merge pull request #2336 from Keerthana-64/ref-args-exact-name · gitpython-developers/GitPython@652cb3f
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/gix-backend.md +20-1Lines changed: 20 additions & 1 deletion - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -374,7 +374,7 @@ and predate the current fallback; they do not establish native coverage. | 374 | 374 | | Worktree inventory, `worktree list` | Main and linked worktree metadata, including bare main repositories and locks | Prunable entries; GIX-14 | | 375 | 375 | | `Repo.merge_base`, `Repo.is_ancestor` | Native graph queries for two revisions | Octopus/fork-point and other options | | 376 | 376 | | `Commit.count` | Reachable commit count, skip/limit/first-parent | Path filters and other revision options | | 377 | | -| `Commit.iter_items`, `Repo.iter_commits` | First-parent walks and a single tip | General history ordering; GIX-8 | | | 377 | +| `Commit.iter_items`, `Repo.iter_commits` | First-parent walks and a single tip | Reflog walks, general history ordering; GIX-8 | | 378 | 378 | | Index entry reads, `ls_files` | Stage/mode/OID/path and exposed index flags | Custom and sparse indexes; GIX-3/4 | | 379 | 379 | | `IndexFile.version`, `update_index` query | Native index version | Actual `update-index` mutations | | 380 | 380 | | Index editing and `IndexFile.write` | Opaque byte snapshots; atomic locked publication | Private-file edits always use Git: arbitrary native index loading and strict pathname validation are missing; GIX-3/4/13/26 | | @@ -392,6 +392,25 @@ and predate the current fallback; they do not establish native coverage. | 392 | 392 | | `Repo.untracked_files` | Native directory walk | Extra status options and non-root command directories | | 393 | 393 | | `Repo.ignored` | Native excludes with tracked-file suppression | Symlink/submodule traversal, unsupported path normalization, and unavailable path metadata | | 394 | 394 |
| | 395 | +Revision APIs preserve a reference object's validated full name. The shared | | 396 | +`Git._check_revision()` first uses the exact reference reader to verify existence | | 397 | +and the symbolic-reference chain; it passes the full name rather than the resolved | | 398 | +object ID. Strings retain Git's name guessing, and commit objects retain their IDs. | | 399 | +This prevents same-named tags from replacing branches without losing the ref | | 400 | +identity needed by reflog walks, fork-point lookups and remote archives. | | 401 | +`merge_base()` validates nested list/tuple operands before string conversion. | | 402 | +`exact-refs.sh` covers these paths with shadowing tags and missing references in | | 403 | +SHA-1/SHA-256 and files/reftable repositories on both backends. | | 404 | + | | 405 | +These name-sensitive operations retain CLI fallback. GixPython 0.3.0's | | 406 | +`Repository.rev_walk()` has no Git-compatible reflog-walk mode, | | 407 | +`Repository.merge_base()` exposes graph merge bases rather than reflog-based | | 408 | +fork-point selection, and no upload-archive client API is exposed. The regressions | | 409 | +compare Git 2.54.0's reflog history, old fork point after an upstream rewind, | | 410 | +and default upload-archive server behavior, which rejects literal object IDs. | | 411 | +Native replacements must expose matching reflog walking and fork-point selection | | 412 | +and the upload-archive protocol; no Python implementation substitutes for them. | | 413 | + | 395 | 414 | Reference reads require an exact name, unlike revision APIs that intentionally | 396 | 415 | apply Git's name guessing. `reference_info()` uses | 397 | 416 | `Repository.try_find_reference()` and verifies the returned name for `HEAD` and | |
Collapse file Copy file name to clipboardExpand all lines: doc/source/changes.rst +8-1Lines changed: 8 additions & 1 deletion - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -57,7 +57,14 @@ API changes | 57 | 57 | * Reference reads require the exact name. A missing branch no longer resolves to | 58 | 58 | a similarly named tag, so an unborn branch's initial commit has no parent. | 59 | 59 | A tag named ``REBASE_HEAD`` no longer reports a rebase in progress. Revision | 60 | | - APIs such as ``Repo.commit(rev)`` retain Git's name guessing. | | 60 | + APIs such as ``Repo.commit(rev)`` retain Git's name guessing for strings. | | 61 | + A reference object passed as a revision, for example to ``Repo.commit()``, | | 62 | + ``Repo.iter_commits()``, ``Repo.merge_base()``, ``IndexFile.from_tree()`` or | | 63 | + ``HEAD.reset()``, is validated by its exact name and passed using its full ref | | 64 | + name, so a tag named like a branch or remote-tracking branch no longer takes | | 65 | + its place. The ref name also preserves reflog walks, fork-point lookups and | | 66 | + remote archives. A missing reference raises ``ValueError``, as its ``object`` | | 67 | + property does. | 61 | 68 | * Packed tag names containing non-UTF-8 bytes are represented losslessly as | 62 | 69 | surrogate-escaped strings. ``safe_encode()`` recovers their original bytes, | 63 | 70 | and Gix reference validation and resolution receive those bytes directly. | |
Collapse file Copy file name to clipboardExpand all lines: git/cmd.py +18Lines changed: 18 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1023,6 +1023,24 @@ def _check_operand(value: Any, label: str = "operand") -> str: | 1023 | 1023 | raise UnsafeOptionError(f"Invalid {label}: {value!r}") | 1024 | 1024 | return value | 1025 | 1025 |
| | 1026 | + @staticmethod | | 1027 | + def _check_revision(value: Any, label: str = "revision") -> str: | | 1028 | + """Validate a revision, preserving a reference object's full name. | | 1029 | +
| | 1030 | + A reference converts to its short name, for which Git's revision lookup tries | | 1031 | + other references, such as a same-named tag, before ``refs/heads/`` and | | 1032 | + ``refs/remotes/``. Verify the exact reference exists before passing its full | | 1033 | + name, retaining its identity for reflog walks and remote archives. | | 1034 | + """ | | 1035 | + from git.refs.symbolic import SymbolicReference | | 1036 | + | | 1037 | + if isinstance(value, SymbolicReference): | | 1038 | + value.dereference_recursive(value.repo, value.path) | | 1039 | + value = value.path | | 1040 | + if isinstance(value, os.PathLike): | | 1041 | + value = os.fsdecode(value) | | 1042 | + return Git._check_operand(value, label) | | 1043 | + | 1026 | 1044 | def _require_version(self) -> None: | 1027 | 1045 | key = None | 1028 | 1046 | if self._version_info_token is not self._refresh_token: | |
Collapse file Copy file name to clipboardExpand all lines: git/diff.py +4-2Lines changed: 4 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -261,8 +261,10 @@ def diff( | 261 | 261 | clusterable_short_options="46abceflmnpqrstuvwzBCDMNRW", | 262 | 262 | ) | 263 | 263 |
| 264 | | - if isinstance(other, str): | 265 | | - other = self.repo.rev_parse(Git._check_operand(other, "revision")).hexsha | | 264 | + from git.refs.symbolic import SymbolicReference | | 265 | + | | 266 | + if isinstance(other, (str, SymbolicReference)): | | 267 | + other = self.repo.rev_parse(Git._check_revision(other)).hexsha | 266 | 268 | native = _backend.tree_diff(self.repo, self, other, paths, create_patch, kwargs) | 267 | 269 | if native is not NotImplemented: | 268 | 270 | return native | |
Collapse file Copy file name to clipboardExpand all lines: git/index/base.py +1-2Lines changed: 1 addition & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -280,8 +280,7 @@ def _tree_oid(self, tree: Treeish) -> str: | 280 | 280 | tree = tree[0] | 281 | 281 | if isinstance(tree, bytes): | 282 | 282 | tree = tree.hex() if len(tree) == self.repo._oid_size else tree.decode("ascii") | 283 | | - value = str(tree) | 284 | | - Git._check_operand(value, "tree revision") | | 283 | + value = Git._check_revision(tree, "tree revision") | 285 | 284 | return self.repo.git._call_process_safe("rev_parse", "--verify", "--end-of-options", value + "^{tree}") | 286 | 285 |
| 287 | 286 | def _index_path(self) -> PathLike: | |
Collapse file Copy file name to clipboardExpand all lines: git/objects/base.py +2-1Lines changed: 2 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 9 | 9 |
| 10 | 10 | import gitdb.typ as dbtyp | 11 | 11 |
| | 12 | +from git.cmd import Git | 12 | 13 | from git.compat import force_text | 13 | 14 | from git.exc import WorkTreeRepositoryUnsupported | 14 | 15 | from git.util import LazyMixin, bin_to_hex, join_path_native, stream_copy | @@ -138,7 +139,7 @@ def new(cls, repo: "Repo", id: Union[str, "Reference"]) -> AnyGitObject: | 138 | 139 | This cannot be a ``__new__`` method as it would always call :meth:`__init__` | 139 | 140 | with the input id which is not necessarily a binsha. | 140 | 141 | """ | 141 | | - return repo.rev_parse(str(id)) | | 142 | + return repo.rev_parse(Git._check_revision(id)) | 142 | 143 |
| 143 | 144 | @classmethod | 144 | 145 | def new_from_sha(cls, repo: "Repo", sha1: bytes) -> AnyGitObject: | |
Collapse file Copy file name to clipboardExpand all lines: git/objects/commit.py +2-2Lines changed: 2 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -465,8 +465,8 @@ def iter_items( | 465 | 465 | args_list.extend(paths_tup) | 466 | 466 | # END if paths | 467 | 467 |
| 468 | | - Git._check_operand(str(rev), "revision") | 469 | | - native = _backend.history(repo.git, str(rev), paths, kwargs) | | 468 | + rev = Git._check_revision(rev) | | 469 | + native = _backend.history(repo.git, rev, paths, kwargs) | 470 | 470 | if native is not NotImplemented: | 471 | 471 | return (cls(repo, hex_to_bin(oid)) for oid in native) | 472 | 472 | proc = repo.git._call_process_safe("rev_list", rev, args_list, as_process=True, **kwargs) | |
Collapse file Copy file name to clipboardExpand all lines: git/refs/head.py +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -121,7 +121,7 @@ def reset( | 121 | 121 | # END working tree handling | 122 | 122 |
| 123 | 123 | try: | 124 | | - oid = self.repo.rev_parse(Git._check_operand(commit, "revision")).hexsha | | 124 | + oid = self.repo.rev_parse(Git._check_revision(commit)).hexsha | 125 | 125 | self.repo.git._call_process_safe("reset", mode, oid, "--", paths, **kwargs) | 126 | 126 | except GitCommandError as e: | 127 | 127 | # git nowadays may use 1 as status to indicate there are still unstaged | |
Collapse file Copy file name to clipboardExpand all lines: git/refs/symbolic.py +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -575,7 +575,7 @@ def _create( | 575 | 575 | ) -> T_References: | 576 | 576 | full_path = cls.to_full_path(path) | 577 | 577 | cls._get_validated_ref_path(repo, full_path) | 578 | | - target = repo.rev_parse(str(reference)) if resolve else reference | | 578 | + target = repo.rev_parse(Git._check_revision(reference)) if resolve else reference | 579 | 579 | ref = cls(repo, full_path) | 580 | 580 | if force: | 581 | 581 | ref.set_reference(target, logmsg) | |
Collapse file Copy file name to clipboardExpand all lines: git/refs/tag.py +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -170,7 +170,7 @@ def create( | 170 | 170 |
| 171 | 171 | name = Git._check_operand(path, "tag") | 172 | 172 | cls._get_validated_ref_path(repo, cls.to_full_path(name)) | 173 | | - reference = Git._check_operand(reference, "revision") | | 173 | + reference = Git._check_revision(reference) | 174 | 174 | oid = repo.rev_parse(reference).hexsha | 175 | 175 | options = [] if allow_unsafe_options else ["--no-sign"] | 176 | 176 | repo.git._call_process_safe("tag", *options, "--", name, oid, **kwargs) | |
|