From 2ddf3f9f416089db1af3a6688eed3e19e900cd0a Mon Sep 17 00:00:00 2001 From: rustaceanrob Date: Wed, 13 May 2026 12:22:28 +0100 Subject: [PATCH 1/3] contrib: add cherry-pick-upstream Rust tool Adds a binary that fetches Bitcoin Core's master branch, finds the common ancestor with the current branch, then attempts to cherry-pick each merged PR (merge commits) in order. Conflicts are logged and skipped; the tool continues to the end and prints a summary. Co-Authored-By: Claude Sonnet 4.6 --- contrib/cherry-pick-upstream/Cargo.lock | 7 ++ contrib/cherry-pick-upstream/Cargo.toml | 6 + contrib/cherry-pick-upstream/README.md | 15 +++ contrib/cherry-pick-upstream/src/main.rs | 143 +++++++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 contrib/cherry-pick-upstream/Cargo.lock create mode 100644 contrib/cherry-pick-upstream/Cargo.toml create mode 100644 contrib/cherry-pick-upstream/README.md create mode 100644 contrib/cherry-pick-upstream/src/main.rs diff --git a/contrib/cherry-pick-upstream/Cargo.lock b/contrib/cherry-pick-upstream/Cargo.lock new file mode 100644 index 000000000000..9d962cd9be29 --- /dev/null +++ b/contrib/cherry-pick-upstream/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cherry-pick-upstream" +version = "0.1.0" diff --git a/contrib/cherry-pick-upstream/Cargo.toml b/contrib/cherry-pick-upstream/Cargo.toml new file mode 100644 index 000000000000..21b139c6c0c2 --- /dev/null +++ b/contrib/cherry-pick-upstream/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "cherry-pick-upstream" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/contrib/cherry-pick-upstream/README.md b/contrib/cherry-pick-upstream/README.md new file mode 100644 index 000000000000..50d1c59d3f38 --- /dev/null +++ b/contrib/cherry-pick-upstream/README.md @@ -0,0 +1,15 @@ +# Auto-merge tool + +This is a tool to fetch commits from an upstream repository and apply the changes if no conflicts exist. Conflicting commit hashes are reported. + +## Usage + +Checkout a feature branch: +``` +git checkout -b daily-cherry-pick +``` + +Run the script +``` +cargo run --release -- --remote=upstream --branch=master +``` diff --git a/contrib/cherry-pick-upstream/src/main.rs b/contrib/cherry-pick-upstream/src/main.rs new file mode 100644 index 000000000000..a629681835b0 --- /dev/null +++ b/contrib/cherry-pick-upstream/src/main.rs @@ -0,0 +1,143 @@ +// Copyright (c) The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/license/mit/. + +use std::process::{Command, ExitCode, Stdio}; + +fn git_output(args: &[&str]) -> Result { + let out = Command::new("git") + .args(args) + .output() + .map_err(|e| e.to_string())?; + if out.status.success() { + Ok(String::from_utf8_lossy(&out.stdout).trim().to_string()) + } else { + Err(String::from_utf8_lossy(&out.stderr).trim().to_string()) + } +} + +// Runs a git command, inheriting stdout/stderr, returning whether it succeeded. +fn git_run(args: &[&str]) -> bool { + Command::new("git") + .args(args) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map(|s| s.success()) + .unwrap_or(false) +} + +fn print_usage() { + eprintln!("Usage: cherry-pick-upstream [--remote=] [--branch=]"); + eprintln!(); + eprintln!(" --remote= Upstream remote name (default: upstream)"); + eprintln!(" --branch= Upstream branch name (default: master)"); +} + +fn main() -> ExitCode { + let args: Vec = std::env::args().skip(1).collect(); + let mut remote = "upstream".to_string(); + let mut branch = "master".to_string(); + + for arg in &args { + if let Some(val) = arg.strip_prefix("--remote=") { + remote = val.to_string(); + } else if let Some(val) = arg.strip_prefix("--branch=") { + branch = val.to_string(); + } else if arg == "--help" || arg == "-h" { + print_usage(); + return ExitCode::SUCCESS; + } else { + eprintln!("Unknown argument: {arg}"); + print_usage(); + return ExitCode::FAILURE; + } + } + + let status = git_output(&["status", "--porcelain"]).unwrap_or_default(); + if !status.is_empty() { + eprintln!("Working tree is not clean. Commit or stash changes first."); + return ExitCode::FAILURE; + } + + println!("Fetching {remote}/{branch}..."); + if !git_run(&["fetch", &remote, &branch]) { + eprintln!("Failed to fetch {remote}/{branch}."); + return ExitCode::FAILURE; + } + + let upstream_ref = format!("{remote}/{branch}"); + + let merge_base = match git_output(&["merge-base", "HEAD", &upstream_ref]) { + Ok(h) => h, + Err(e) => { + eprintln!("Failed to find common ancestor: {e}"); + return ExitCode::FAILURE; + } + }; + println!("Common ancestor: {merge_base}"); + + // Collect merge commits (merged PRs) from merge base to upstream HEAD, oldest first. + let range = format!("{merge_base}..{upstream_ref}"); + let log = match git_output(&["log", "--merges", "--oneline", "--reverse", &range]) { + Ok(out) => out, + Err(e) => { + eprintln!("Failed to list upstream commits: {e}"); + return ExitCode::FAILURE; + } + }; + + if log.is_empty() { + println!("Already up to date with {upstream_ref}."); + return ExitCode::SUCCESS; + } + + let commits: Vec<&str> = log.lines().collect(); + println!("Found {} merge commits to cherry-pick.\n", commits.len()); + + let mut succeeded = 0usize; + let mut conflicts: Vec = Vec::new(); + + for line in &commits { + let hash = match line.split_whitespace().next() { + Some(h) => h, + None => continue, + }; + let subject: String = line + .trim_start_matches(hash) + .trim() + .chars() + .take(72) + .collect(); + + print!("{hash} {subject} ... "); + + // -m 1: treat parent 1 (mainline) as the base when cherry-picking a merge commit. + // -x: append "cherry picked from commit " to the message. + if git_run(&["cherry-pick", "-m", "1", "-x", hash]) { + println!("ok"); + succeeded += 1; + } else { + println!("CONFLICT"); + conflicts.push(hash.to_string()); + git_run(&["cherry-pick", "--abort"]); + } + } + + println!(); + println!( + "Done: {} applied, {} conflicted.", + succeeded, + conflicts.len() + ); + + if !conflicts.is_empty() { + println!(); + println!("Conflicting commits (skipped):"); + for hash in &conflicts { + println!(" {hash}"); + } + } + + ExitCode::SUCCESS +} From ca8274c7928b6b8ac5714347598613a3724d43ee Mon Sep 17 00:00:00 2001 From: merge-script Date: Wed, 13 May 2026 10:27:38 +0200 Subject: [PATCH 2/3] Merge bitcoin/bitcoin#34547: lint: modernise lint tooling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2424e5283672e10bc45cdbca1a8851308716b50c lint: doc: detail lint tool install methods (will) 5fefa5a654e2a1146c8abc9673e72d7bcbf2f757 Don't pin Python patch version (Sjors Provoost) fd15b55c2ef607525d45f26ab3e7f3fc600e29af lint: use requirements.txt (will) 5f4d3383daa064d188ef8c6b1d9adbad3a67bcb6 lint: switch to ruff for formatting and linting (will) a53b81ce4e649dd637a217686745a6f6c6c81ca5 lint: switch to uv for python management in linter (will) Pull request description: Modernise our lint tooling by: \- Replacing pyenv + pip with [uv](https://docs.astral.sh/uv/) for better Python environment and dependency management \- Move uv ruff and ty to install via `COPY --from` multi-stage Docker image imports \- Moving ruff lint rules from hardcoded Rust array (in lint_py.rs) into a top-level ruff.toml \- Extracting all remaining pip dependencies into dedicated ci/lint/requirements.txt Extra rationale: `COPY --from` pulls pre-built binaries from upstream images instead of compiling/downloading at runtime. Containerfile layer optimisations reduce rebuild frequency further. Pinning tool versions in the dockerfile makes it more excplicit and easier to find. The tradeoff we make here is that there is no longer a single install script to install tooling on a local machine. However I think this is OK, as it currently only works for `apt`-based OSes anyway, and I don't think running the linter outside of the container is such a valuable use-case as it is with some of the other CI jobs. ACKs for top commit: maflcko: review ACK 2424e5283672e10bc45cdbca1a8851308716b50c 🗿 sedited: ACK 2424e5283672e10bc45cdbca1a8851308716b50c Tree-SHA512: 32ef989c1e241cebe5f13da10abd23f6f63306591fd1f81880d688b886082bca17987591dc592c41fbb72278eba57b3cc6e786de7cfa80eb490ab34465d0119b (cherry picked from commit 09a9bb3536718d0dffa6f797ddff907df6ef94f9) --- .python-version | 2 +- ci/lint/01_install.sh | 25 +++------------ ci/lint/06_script.sh | 2 +- ci/lint/requirements.txt | 3 ++ ci/lint_imagefile | 7 +++++ ruff.toml | 42 +++++++++++++++++++++++++ test/lint/README.md | 2 +- test/lint/test_runner/src/lint_py.rs | 47 +--------------------------- 8 files changed, 61 insertions(+), 69 deletions(-) create mode 100644 ci/lint/requirements.txt create mode 100644 ruff.toml diff --git a/.python-version b/.python-version index 1445aee866ce..c8cfe3959183 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10.14 +3.10 diff --git a/ci/lint/01_install.sh b/ci/lint/01_install.sh index 573dbca5fe56..9c2020a9a15d 100755 --- a/ci/lint/01_install.sh +++ b/ci/lint/01_install.sh @@ -22,29 +22,14 @@ ${CI_RETRY_EXE} apt-get update # - moreutils (used by scripted-diff) ${CI_RETRY_EXE} apt-get install -y cargo curl xz-utils git gpg moreutils -PYTHON_PATH="/python_build" -if [ ! -d "${PYTHON_PATH}/bin" ]; then - ( - ${CI_RETRY_EXE} git clone --depth=1 https://github.com/pyenv/pyenv.git - cd pyenv/plugins/python-build || exit 1 - ./install.sh - ) - # For dependencies see https://github.com/pyenv/pyenv/wiki#suggested-build-environment - ${CI_RETRY_EXE} apt-get install -y build-essential libssl-dev zlib1g-dev \ - libbz2-dev libreadline-dev libsqlite3-dev curl llvm \ - libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev \ - clang - env CC=clang python-build "$(cat "/.python-version")" "${PYTHON_PATH}" -fi -export PATH="${PYTHON_PATH}/bin:${PATH}" +# Install Python and create venv using uv (reads version from .python-version) +uv venv /python_env + +export PATH="/python_env/bin:${PATH}" command -v python3 python3 --version -${CI_RETRY_EXE} pip3 install \ - lief==0.17.5 \ - mypy==1.19.1 \ - pyzmq==27.1.0 \ - ruff==0.15.5 +uv pip install --python /python_env --requirements /ci/lint/requirements.txt SHELLCHECK_VERSION=v0.11.0 curl --fail -L "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.$(uname --machine).tar.xz" | \ diff --git a/ci/lint/06_script.sh b/ci/lint/06_script.sh index 1b36fada9fc6..a0f2dc88bc47 100755 --- a/ci/lint/06_script.sh +++ b/ci/lint/06_script.sh @@ -12,7 +12,7 @@ set -o errexit -o pipefail -o xtrace # of the mounted bitcoin src dir. git config --global --add safe.directory /bitcoin -export PATH="/python_build/bin:${PATH}" +export PATH="/python_env/bin:${PATH}" if [ -n "${LINT_CI_IS_PR}" ]; then export COMMIT_RANGE="HEAD~..HEAD" diff --git a/ci/lint/requirements.txt b/ci/lint/requirements.txt new file mode 100644 index 000000000000..e8abf041dc76 --- /dev/null +++ b/ci/lint/requirements.txt @@ -0,0 +1,3 @@ +lief==0.17.5 +mypy==1.19.1 +pyzmq==27.1.0 diff --git a/ci/lint_imagefile b/ci/lint_imagefile index 77e9688c6506..a6c49a7c8619 100644 --- a/ci/lint_imagefile +++ b/ci/lint_imagefile @@ -6,8 +6,15 @@ FROM mirror.gcr.io/ubuntu:24.04 +# Pin uv and ruff to minor version to avoid breaking changes +# https://docs.astral.sh/uv/reference/policies/versioning/ +# https://docs.astral.sh/ruff/versioning/ +COPY --from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/ruff:0.15 /ruff /bin/ + COPY ./ci/retry/retry /ci_retry COPY ./.python-version /.python-version +COPY ./ci/lint/requirements.txt /ci/lint/requirements.txt COPY ./ci/lint/01_install.sh /install.sh RUN /install.sh && \ diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000000..10df372c7a8d --- /dev/null +++ b/ruff.toml @@ -0,0 +1,42 @@ +[lint] +select = [ + "B006", # mutable-argument-default + "B008", # function-call-in-default-argument + "E101", # indentation contains mixed spaces and tabs + "E401", # multiple imports on one line + "E402", # module level import not at top of file + "E701", # multiple statements on one line (colon) + "E702", # multiple statements on one line (semicolon) + "E703", # statement ends with a semicolon + "E711", # comparison to None should be 'if cond is None:' + "E713", # test for membership should be "not in" + "E714", # test for object identity should be "is not" + "E721", # do not compare types, use "isinstance()" + "E722", # do not use bare 'except' + "E742", # do not define classes named "l", "O", or "I" + "E743", # do not define functions named "l", "O", or "I" + "F401", # module imported but unused + "F402", # import module from line N shadowed by loop variable + "F403", # 'from foo_module import *' used; unable to detect undefined names + "F404", # future import(s) name after other statements + "F405", # foo_function may be undefined, or defined from star imports: bar_module + "F406", # "from module import *" only allowed at module level + "F407", # an undefined __future__ feature name was imported + "F541", # f-string without any placeholders + "F601", # dictionary key name repeated with different values + "F602", # dictionary key variable name repeated with different values + "F621", # too many expressions in an assignment with star-unpacking + "F631", # assertion test is a tuple, which are always True + "F632", # use ==/!= to compare str, bytes, and int literals + "F811", # redefinition of unused name from line N + "F821", # undefined name 'Foo' + "F822", # undefined name name in __all__ + "F823", # local variable name referenced before assignment + "F841", # local variable 'foo' is assigned to but never used + "PLE", # Pylint errors + "W191", # indentation contains tabs + "W291", # trailing whitespace + "W292", # no newline at end of file + "W293", # blank line contains whitespace + "W605", # invalid escape sequence "x" +] diff --git a/test/lint/README.md b/test/lint/README.md index 08703ab8efb0..d060df110e6b 100644 --- a/test/lint/README.md +++ b/test/lint/README.md @@ -56,7 +56,7 @@ or `--help`: | `py_lint` | [ruff](https://github.com/astral-sh/ruff) | markdown link check | [mlc](https://github.com/becheran/mlc) -In use versions and install instructions are available in the [CI setup](../../ci/lint/01_install.sh). +Dependency versions and installation instructions are available in the [CI setup](../../ci/lint/01_install.sh) and the [lint_imagefile](../../ci/lint_imagefile) (for tools where an OCI imagefile exists). Please be aware that on Linux distributions all dependencies are usually available as packages, but could be outdated. diff --git a/test/lint/test_runner/src/lint_py.rs b/test/lint/test_runner/src/lint_py.rs index 7c5c0dae5a65..6a97da6a7721 100644 --- a/test/lint/test_runner/src/lint_py.rs +++ b/test/lint/test_runner/src/lint_py.rs @@ -9,51 +9,6 @@ use crate::util::{check_output, get_pathspecs_default_excludes, git, LintResult} pub fn lint_py_lint() -> LintResult { let bin_name = "ruff"; - let checks = format!( - "--select={}", - [ - "B006", // mutable-argument-default - "B008", // function-call-in-default-argument - "E101", // indentation contains mixed spaces and tabs - "E401", // multiple imports on one line - "E402", // module level import not at top of file - "E701", // multiple statements on one line (colon) - "E702", // multiple statements on one line (semicolon) - "E703", // statement ends with a semicolon - "E711", // comparison to None should be 'if cond is None:' - "E713", // test for membership should be "not in" - "E714", // test for object identity should be "is not" - "E721", // do not compare types, use "isinstance()" - "E722", // do not use bare 'except' - "E742", // do not define classes named "l", "O", or "I" - "E743", // do not define functions named "l", "O", or "I" - "F401", // module imported but unused - "F402", // import module from line N shadowed by loop variable - "F403", // 'from foo_module import *' used; unable to detect undefined names - "F404", // future import(s) name after other statements - "F405", // foo_function may be undefined, or defined from star imports: bar_module - "F406", // "from module import *" only allowed at module level - "F407", // an undefined __future__ feature name was imported - "F541", // f-string without any placeholders - "F601", // dictionary key name repeated with different values - "F602", // dictionary key variable name repeated with different values - "F621", // too many expressions in an assignment with star-unpacking - "F631", // assertion test is a tuple, which are always True - "F632", // use ==/!= to compare str, bytes, and int literals - "F811", // redefinition of unused name from line N - "F821", // undefined name 'Foo' - "F822", // undefined name name in __all__ - "F823", // local variable name … referenced before assignment - "F841", // local variable 'foo' is assigned to but never used - "PLE", // Pylint errors - "W191", // indentation contains tabs - "W291", // trailing whitespace - "W292", // no newline at end of file - "W293", // blank line contains whitespace - "W605", // invalid escape sequence "x" - ] - .join(",") - ); let files = check_output( git() .args(["ls-files", "--", "*.py"]) @@ -61,7 +16,7 @@ pub fn lint_py_lint() -> LintResult { )?; let mut cmd = Command::new(bin_name); - cmd.args(["check", &checks]).args(files.lines()); + cmd.arg("check").args(files.lines()); match cmd.status() { Ok(status) if status.success() => Ok(()), From 2459d614a6c5214ca1e1766037872493d843c1a7 Mon Sep 17 00:00:00 2001 From: merge-script Date: Wed, 13 May 2026 12:19:16 +0100 Subject: [PATCH 3/3] Merge bitcoin/bitcoin#35277: ci: Enable ruff ambiguous-unicode-character checks fa9c919678c0d4926ff735eecf12562d99a7e691 refactor: Use ignore-list over verbose select-list (MarcoFalke) fa9b01adecce7d2e2f0091a363bcf98cf5d5c378 ci: Enable ruff ambiguous-unicode-character checks (MarcoFalke) Pull request description: Ambiguous unicode chars are unused and confusing. Worst, they can lead to bugs. So enable the ruff checks to catch them. Can be tested via: ``` echo 'ZGlmZiAtLWdpdCBhL3Rlc3QvZnVuY3Rpb25hbC93YWxsZXRfZGlzYWJsZS5weSBiL3Rlc3QvZnVu Y3Rpb25hbC93YWxsZXRfZGlzYWJsZS5weQppbmRleCBkYmNjY2Q0Li4wYjhjNDQ2IDEwMDc1NQot LS0gYS90ZXN0L2Z1bmN0aW9uYWwvd2FsbGV0X2Rpc2FibGUucHkKKysrIGIvdGVzdC9mdW5jdGlv bmFsL3dhbGxldF9kaXNhYmxlLnB5CkBAIC0yMSwzICsyMSw4IEBAIGNsYXNzIERpc2FibGVXYWxs ZXRUZXN0IChCaXRjb2luVGVzdEZyYW1ld29yayk6CiAgICAgZGVmIHJ1bl90ZXN0IChzZWxmKToK KyAgICAgICAgIiIiQSBsb3ZlbHkgZG9jc3RyaW5nICh3aXRoIGEgYFUrRkYwOWAgcGFyZW50aGVz aXPvvIkuIiIiCiAgICAgICAgICMgTWFrZSBzdXJlIHdhbGxldCBpcyByZWFsbHkgZGlzYWJsZWQK KyAgICAgICAgIyBu0L5xYSAgIzwtIGlzIEN5cmlsbGljIChgVSswNDNFYCkKKyAgICAgICAgcHJp bnQoIs6XZWxsbywgd29ybGQhIikgICMgPC0gaXMgdGhlIEdyZWVrIGV0YSAoYFUrMDM5N2ApLgor ICAgICAgICBleGFtcGxlID0gInjigI8iICogMTAwICAjICAgICLigI94IiBpcyBhc3NpZ25lZAor ICAgICAgICBleGFtcGxlPU5vbmUjbm9xYQogICAgICAgICBhc3NlcnRfcmFpc2VzX3JwY19lcnJv cigtMzI2MDEsICdNZXRob2Qgbm90IGZvdW5kJywgc2VsZi5ub2Rlc1swXS5nZXR3YWxsZXRpbmZv KQo=' | base64 --decode | git apply git diff ruff check ./test/functional/*.py ``` It should print 4 error types. ACKs for top commit: stickies-v: ACK fa9c919678c0d4926ff735eecf12562d99a7e691 willcl-ark: ACK fa9c919678c0d4926ff735eecf12562d99a7e691 Tree-SHA512: de226ec2feaf65a0a8b15606708cc390296be4492f41221f8a49f034b16e8fb62125342c6993f9d5c76bd4ae2db7343851b252a1b9140e27d6777f19a0b1605e (cherry picked from commit 82733e61deea24cfd344aed36020675a15cc672e) --- ruff.toml | 48 ++++++++++++------------------------------------ 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/ruff.toml b/ruff.toml index 10df372c7a8d..d379dde1f501 100644 --- a/ruff.toml +++ b/ruff.toml @@ -2,41 +2,17 @@ select = [ "B006", # mutable-argument-default "B008", # function-call-in-default-argument - "E101", # indentation contains mixed spaces and tabs - "E401", # multiple imports on one line - "E402", # module level import not at top of file - "E701", # multiple statements on one line (colon) - "E702", # multiple statements on one line (semicolon) - "E703", # statement ends with a semicolon - "E711", # comparison to None should be 'if cond is None:' - "E713", # test for membership should be "not in" - "E714", # test for object identity should be "is not" - "E721", # do not compare types, use "isinstance()" - "E722", # do not use bare 'except' - "E742", # do not define classes named "l", "O", or "I" - "E743", # do not define functions named "l", "O", or "I" - "F401", # module imported but unused - "F402", # import module from line N shadowed by loop variable - "F403", # 'from foo_module import *' used; unable to detect undefined names - "F404", # future import(s) name after other statements - "F405", # foo_function may be undefined, or defined from star imports: bar_module - "F406", # "from module import *" only allowed at module level - "F407", # an undefined __future__ feature name was imported - "F541", # f-string without any placeholders - "F601", # dictionary key name repeated with different values - "F602", # dictionary key variable name repeated with different values - "F621", # too many expressions in an assignment with star-unpacking - "F631", # assertion test is a tuple, which are always True - "F632", # use ==/!= to compare str, bytes, and int literals - "F811", # redefinition of unused name from line N - "F821", # undefined name 'Foo' - "F822", # undefined name name in __all__ - "F823", # local variable name referenced before assignment - "F841", # local variable 'foo' is assigned to but never used + "E", # pycodestyle errors + "F", # pyflakes errors "PLE", # Pylint errors - "W191", # indentation contains tabs - "W291", # trailing whitespace - "W292", # no newline at end of file - "W293", # blank line contains whitespace - "W605", # invalid escape sequence "x" + "RUF001", # ambiguous-unicode-character-string + "RUF002", # ambiguous-unicode-character-docstring + "RUF003", # ambiguous-unicode-character-comment + "W", # pycodestyle warnings +] +ignore = [ + "E501", # line too long + "E712", # true-false comparison + "E731", # lambda assignment + "E741", # ambiguous-variable-name ]