From 1b79d449c90cc6fca8220246e2de159d24666837 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 8 Jun 2025 14:33:58 -0400 Subject: [PATCH 1/3] Improve line-ending consistency in requirements files `requirements-dev.txt`, but none of the others, was tracked with Windows-style (CRLF) line endings. This appears to have been the case since it was introduced in a1b7634 (as `dev-requirements.txt`) and not to be intentional. This only changes how it is stored in the repository. This does not change `.gitattributes` (it is not forced to have LF line endings if automatic line-ending conversions are configured in Git). --- requirements-dev.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f626644af..01cb2d040 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,8 +1,8 @@ --r requirements.txt --r test-requirements.txt - -# For additional local testing/linting - to be added elsewhere eventually. -ruff -shellcheck -pytest-icdiff -# pytest-profiling +-r requirements.txt +-r test-requirements.txt + +# For additional local testing/linting - to be added elsewhere eventually. +ruff +shellcheck +pytest-icdiff +# pytest-profiling From 6f4f7f5137d63facb61eae2955e6c0801c71b7b5 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 8 Jun 2025 15:05:03 -0400 Subject: [PATCH 2/3] Update Ruff configuration This resolves two warnings about Ruff configuration, by: - No longer setting `ignore-init-module-imports = true` explicitly, which was deprecated since `ruff` 0.4.4. We primarily use `ruff` via `pre-commit`, for which this deprecation has applied since we upgraded the version in `.pre-commit-config.yaml` from 0.4.3 to 0.6.0 in d1582d1 (#1953). We continue to list `F401` ("Module imported but unused") as not automatically fixable, to avoid inadvertently removing imports that may be needed. See also: https://docs.astral.sh/ruff/settings/#lint_ignore-init-module-imports - Rename the rule `TCH004` to `TC004`, since `TCH004` is the old name that may eventually be removed and that is deprecated since 0.8.0. We upgraded `ruff` in `.pre-commit-config.yml` again in b7ce712 (#2031), from 0.6.0 to 0.11.12, at which point this deprecation applied. See also https://astral.sh/blog/ruff-v0.8.0. These changes make those configuration-related warnings go away, and no new diagnostics (errors/warnings) are produced when running `ruff check` or `pre-commit run --all-files`. No F401-related diagnostics are triggered when testing with explicit `ignore-init-module-imports = false`, in preview mode or otherwise. In addition, this commit makes two changes that are not needed to resolve warnings: - Stop excluding `E203` ("Whitespace before ':'"). That diagnostic is no longer failing with the current code here in the current version of `ruff`, and code changes that would cause it to fail would likely be accidentally mis-st - Add the version lower bound `>=0.8` for `ruff` in `requirements-dev.txt`. That file is rarely used, as noted in a8a73ff7 (#1871), but as long as we have it, there may be a benefit to excluding dependency versions for which our configuration is no longer compatible. This is the only change in this commit outside of `pyproject.toml`. --- pyproject.toml | 10 ++++------ requirements-dev.txt | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 090972eed..0097e9951 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,16 +60,14 @@ lint.select = [ # "UP", # See: https://docs.astral.sh/ruff/rules/#pyupgrade-up ] lint.extend-select = [ - # "A", # See: https://pypi.org/project/flake8-builtins - "B", # See: https://pypi.org/project/flake8-bugbear - "C4", # See: https://pypi.org/project/flake8-comprehensions - "TCH004", # See: https://docs.astral.sh/ruff/rules/runtime-import-in-type-checking-block/ + # "A", # See: https://pypi.org/project/flake8-builtins + "B", # See: https://pypi.org/project/flake8-bugbear + "C4", # See: https://pypi.org/project/flake8-comprehensions + "TC004", # See: https://docs.astral.sh/ruff/rules/runtime-import-in-type-checking-block/ ] lint.ignore = [ - "E203", # Whitespace before ':' "E731", # Do not assign a `lambda` expression, use a `def` ] -lint.ignore-init-module-imports = true lint.unfixable = [ "F401", # Module imported but unused ] diff --git a/requirements-dev.txt b/requirements-dev.txt index 01cb2d040..066b192b8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,7 @@ -r test-requirements.txt # For additional local testing/linting - to be added elsewhere eventually. -ruff +ruff >=0.8 shellcheck pytest-icdiff # pytest-profiling From a36b8a5a726ee5a17cfd492e49c0b0b2a05b2136 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sun, 8 Jun 2025 16:25:17 -0400 Subject: [PATCH 3/3] Always use a `def` instead of assigning a `lambda` This stops listing Ruff rule `E731` ("Do not assign a `lambda` expression, use a `def`") as ignored, and fixes all occurrences of it: - Spacing is manually adjusted so that readability is not harmed, while still satisfying the current formatting conventions. - Although the affected test modules do not currently use type annotations, the non-test modules do. Some of the lambdas already had type annotations, by annotating the variable itself with an expression formed by subscripting `Callable`. This change preserves them, converting them to paramter and return type annotations in the resulting `def`. Where such type annotations were absent (in lambdas in non-test modules), or partly absent, all missing annotations are added to the `def`. - Unused paramters are prefixed with a `_`. - `IndexFile.checkout` assigned a lambda to `make_exc`, whose body was somewhat difficult to read. Separately from converting it to a `def`, this refactors the expression in the `return` statement to use code like `(x, *ys)` in place of `(x,) + tuple(ys)`. This change does not appear to have introduced (nor fixed) any `mypy` errors. This only affects lambdas that were assigned directly to variables. Other lambda expressions remain unchanged. --- git/index/base.py | 19 +++++++++++++++---- git/objects/tree.py | 4 +++- pyproject.toml | 2 +- test/test_fun.py | 6 +++++- test/test_index.py | 5 ++++- test/test_tree.py | 10 ++++++++-- 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/git/index/base.py b/git/index/base.py index a95762dca..7cc9d3ade 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -530,7 +530,10 @@ def unmerged_blobs(self) -> Dict[PathLike, List[Tuple[StageType, Blob]]]: stage. That is, a file removed on the 'other' branch whose entries are at stage 3 will not have a stage 3 entry. """ - is_unmerged_blob = lambda t: t[0] != 0 + + def is_unmerged_blob(t: Tuple[StageType, Blob]) -> bool: + return t[0] != 0 + path_map: Dict[PathLike, List[Tuple[StageType, Blob]]] = {} for stage, blob in self.iter_blobs(is_unmerged_blob): path_map.setdefault(blob.path, []).append((stage, blob)) @@ -690,12 +693,17 @@ def _store_path(self, filepath: PathLike, fprogress: Callable) -> BaseIndexEntry This must be ensured in the calling code. """ st = os.lstat(filepath) # Handles non-symlinks as well. + if S_ISLNK(st.st_mode): # In PY3, readlink is a string, but we need bytes. # In PY2, it was just OS encoded bytes, we assumed UTF-8. - open_stream: Callable[[], BinaryIO] = lambda: BytesIO(force_bytes(os.readlink(filepath), encoding=defenc)) + def open_stream() -> BinaryIO: + return BytesIO(force_bytes(os.readlink(filepath), encoding=defenc)) else: - open_stream = lambda: open(filepath, "rb") + + def open_stream() -> BinaryIO: + return open(filepath, "rb") + with open_stream() as stream: fprogress(filepath, False, filepath) istream = self.repo.odb.store(IStream(Blob.type, st.st_size, stream)) @@ -1336,8 +1344,11 @@ def handle_stderr(proc: "Popen[bytes]", iter_checked_out_files: Iterable[PathLik kwargs["as_process"] = True kwargs["istream"] = subprocess.PIPE proc = self.repo.git.checkout_index(args, **kwargs) + # FIXME: Reading from GIL! - make_exc = lambda: GitCommandError(("git-checkout-index",) + tuple(args), 128, proc.stderr.read()) + def make_exc() -> GitCommandError: + return GitCommandError(("git-checkout-index", *args), 128, proc.stderr.read()) + checked_out_files: List[PathLike] = [] for path in paths: diff --git a/git/objects/tree.py b/git/objects/tree.py index 09184a781..1845d0d0d 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -50,7 +50,9 @@ # -------------------------------------------------------- -cmp: Callable[[str, str], int] = lambda a, b: (a > b) - (a < b) + +def cmp(a: str, b: str) -> int: + return (a > b) - (a < b) class TreeModifier: diff --git a/pyproject.toml b/pyproject.toml index 0097e9951..58ed81f17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ lint.extend-select = [ "TC004", # See: https://docs.astral.sh/ruff/rules/runtime-import-in-type-checking-block/ ] lint.ignore = [ - "E731", # Do not assign a `lambda` expression, use a `def` + # If it becomes necessary to ignore any rules, list them here. ] lint.unfixable = [ "F401", # Module imported but unused diff --git a/test/test_fun.py b/test/test_fun.py index b8593b400..a456b8aab 100644 --- a/test/test_fun.py +++ b/test/test_fun.py @@ -243,6 +243,7 @@ def test_tree_traversal(self): B_old = self.rorepo.tree("1f66cfbbce58b4b552b041707a12d437cc5f400a") # old base tree # Two very different trees. + entries = traverse_trees_recursive(odb, [B_old.binsha, H.binsha], "") self._assert_tree_entries(entries, 2) @@ -251,7 +252,10 @@ def test_tree_traversal(self): self._assert_tree_entries(oentries, 2) # Single tree. - is_no_tree = lambda i, d: i.type != "tree" + + def is_no_tree(i, _d): + return i.type != "tree" + entries = traverse_trees_recursive(odb, [B.binsha], "") assert len(entries) == len(list(B.traverse(predicate=is_no_tree))) self._assert_tree_entries(entries, 1) diff --git a/test/test_index.py b/test/test_index.py index c42032e70..cf3b90fa6 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -330,7 +330,10 @@ def test_index_file_from_tree(self, rw_repo): assert len([e for e in three_way_index.entries.values() if e.stage != 0]) # ITERATE BLOBS - merge_required = lambda t: t[0] != 0 + + def merge_required(t): + return t[0] != 0 + merge_blobs = list(three_way_index.iter_blobs(merge_required)) assert merge_blobs assert merge_blobs[0][0] in (1, 2, 3) diff --git a/test/test_tree.py b/test/test_tree.py index 73158113d..7ba93bd36 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -126,12 +126,18 @@ def test_traverse(self): assert len(list(root)) == len(list(root.traverse(depth=1))) # Only choose trees. - trees_only = lambda i, d: i.type == "tree" + + def trees_only(i, _d): + return i.type == "tree" + trees = list(root.traverse(predicate=trees_only)) assert len(trees) == len([i for i in root.traverse() if trees_only(i, 0)]) # Test prune. - lib_folder = lambda t, d: t.path == "lib" + + def lib_folder(t, _d): + return t.path == "lib" + pruned_trees = list(root.traverse(predicate=trees_only, prune=lib_folder)) assert len(pruned_trees) < len(trees)