diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6c5049e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5b44ede..2db1804 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,34 +4,60 @@ on: - cron: "0 */4 * * *" workflow_dispatch: repository_dispatch: - types: [ pypi_release ] + types: [pypi_release] + +permissions: {} jobs: build: name: main runs-on: ubuntu-latest + permissions: + contents: write steps: - if: ${{ startsWith(github.event_name, 'repository_dispatch') }} run: sleep 30 - - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - python-version: "3.x" + persist-credentials: true # needed to push commits below - - run: pip install pre-commit-mirror-maker + - name: Install uv + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7.2.1 - name: set git config run: | - git config user.name 'Github Actions' - git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com' - - - run: pre-commit-mirror --language python --package-name ruff --entry 'ruff check --force-exclude' --id ruff --types-or python --types-or pyi --require-serial --description "Run 'ruff' for extremely fast Python linting" . + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - - run: git push origin HEAD:refs/heads/main --tags + - run: uv run --no-project mirror.py - - name: "Update version in README.md" - run: python update_version.py + - name: check for unpushed commits + id: check_unpushed + run: | + UNPUSHED_COMMITS=$(git log origin/main..HEAD) + if [ -z "$UNPUSHED_COMMITS" ]; then + echo "No unpushed commits found." + echo "changes_exist=false" >> $GITHUB_ENV + else + echo "Unpushed commits found." + echo "changes_exist=true" >> $GITHUB_ENV + fi + + - name: push changes if they exist + if: env.changes_exist == 'true' + run: | + git push origin HEAD:refs/heads/main + git push origin HEAD:refs/heads/main --tags - - run: git push origin HEAD:refs/heads/main + - name: create release on new tag if new changes exist + if: env.changes_exist == 'true' + run: | + TAG_NAME=$(git describe --tags $(git rev-list --tags --max-count=1)) + echo $TAG_NAME + gh release create "$TAG_NAME" \ + --title "$TAG_NAME" \ + --notes "See: https://github.com/astral-sh/ruff/releases/tag/${TAG_NAME/v}" \ + --latest + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index b6e4761..309de57 100644 --- a/.gitignore +++ b/.gitignore @@ -1,129 +1,3 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env +__pycache__ +*.pyc .venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 3dce9c5..4d17023 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,19 +1,32 @@ -- id: ruff - name: ruff - description: "Run 'ruff' for extremely fast Python linting" +- id: ruff-check + name: ruff check + description: "Run 'ruff check' for extremely fast Python linting" entry: ruff check --force-exclude language: python - "types_or": [python, pyi] + types_or: [python, pyi, jupyter] args: [] require_serial: true additional_dependencies: [] minimum_pre_commit_version: "2.9.2" + - id: ruff-format - name: ruff-format + name: ruff format description: "Run 'ruff format' for extremely fast Python formatting" entry: ruff format --force-exclude language: python - "types_or": [python, pyi] + types_or: [python, pyi, jupyter] + args: [] + require_serial: true + additional_dependencies: [] + minimum_pre_commit_version: "2.9.2" + +# Legacy alias +- id: ruff + name: ruff (legacy alias) + description: "Run 'ruff check' for extremely fast Python linting" + entry: ruff check --force-exclude + language: python + types_or: [python, pyi, jupyter] args: [] require_serial: true additional_dependencies: [] diff --git a/.version b/.version deleted file mode 100644 index ef63c3f..0000000 --- a/.version +++ /dev/null @@ -1 +0,0 @@ -0.0.290 diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE b/LICENSE-MIT similarity index 96% rename from LICENSE rename to LICENSE-MIT index ac22094..c16d8bb 100644 --- a/LICENSE +++ b/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Charlie Marsh +Copyright (c) 2024 Astral Software Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 19159ae..eebc05b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # ruff-pre-commit [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) -[![image](https://img.shields.io/pypi/v/ruff/0.0.290.svg)](https://pypi.python.org/pypi/ruff) -[![image](https://img.shields.io/pypi/l/ruff/0.0.290.svg)](https://pypi.python.org/pypi/ruff) -[![image](https://img.shields.io/pypi/pyversions/ruff/0.0.290.svg)](https://pypi.python.org/pypi/ruff) +[![image](https://img.shields.io/pypi/v/ruff/0.15.0.svg)](https://pypi.python.org/pypi/ruff) +[![image](https://img.shields.io/pypi/l/ruff/0.15.0.svg)](https://pypi.python.org/pypi/ruff) +[![image](https://img.shields.io/pypi/pyversions/ruff/0.15.0.svg)](https://pypi.python.org/pypi/ruff) [![Actions status](https://github.com/astral-sh/ruff-pre-commit/workflows/main/badge.svg)](https://github.com/astral-sh/ruff-pre-commit/actions) A [pre-commit](https://pre-commit.com/) hook for [Ruff](https://github.com/astral-sh/ruff). @@ -13,71 +13,75 @@ Distributed as a standalone repository to enable installing Ruff via prebuilt wh ### Using Ruff with pre-commit -Add this to your `.pre-commit-config.yaml`: +To run Ruff's [linter](https://docs.astral.sh/ruff/linter) and [formatter](https://docs.astral.sh/ruff/formatter) +(available as of Ruff v0.0.289) via pre-commit, add the following to your `.pre-commit-config.yaml`: ```yaml +repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.0.290 + rev: v0.15.0 hooks: - - id: ruff + # Run the linter. + - id: ruff-check + # Run the formatter. + - id: ruff-format ``` -Or, to enable autofix: +To enable lint fixes, add the `--fix` argument to the lint hook: ```yaml +repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.0.290 + rev: v0.15.0 hooks: - - id: ruff - args: [--fix, --exit-non-zero-on-fix] + # Run the linter. + - id: ruff-check + args: [ --fix ] + # Run the formatter. + - id: ruff-format ``` -To run the hook on Jupyter Notebooks too: +To avoid running on Jupyter Notebooks, remove `jupyter` from the list of allowed filetypes: ```yaml +repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.0.290 + rev: v0.15.0 hooks: - - id: ruff - types_or: [python, pyi, jupyter] + # Run the linter. + - id: ruff-check + types_or: [ python, pyi ] + args: [ --fix ] + # Run the formatter. + - id: ruff-format + types_or: [ python, pyi ] ``` -Ruff's pre-commit hook should be placed after other formatting tools, such as Black and isort, -_unless_ you enable autofix, in which case, Ruff's pre-commit hook should run _before_ Black, isort, -and other formatting tools, as Ruff's autofix behavior can output code changes that require -reformatting. +When running with `--fix`, Ruff's lint hook should be placed _before_ Ruff's formatter hook, and +_before_ Black, isort, and other formatting tools, as Ruff's fix behavior can output code changes +that require reformatting. -### Using Ruff's formatter (unstable) +When running without `--fix`, Ruff's formatter hook can be placed before or after Ruff's lint hook. -[Ruff's formatter](https://github.com/astral-sh/ruff/blob/main/crates/ruff_python_formatter/README.md) is in Alpha, but can used with pre-commit by adding the `ruff-format` hook: - -```yaml -- repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.0.290 - hooks: - - id: ruff-format -``` +(As long as your Ruff configuration avoids any [linter-formatter incompatibilities](https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules), +`ruff format` should never introduce new lint errors, so it's safe to run Ruff's format hook _after_ +`ruff check --fix`.) -To check formatting without changing files, use `--check`: +## License -```yaml -- repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.0.290 - hooks: - - id: ruff-format - args: [--check] -``` +ruff-pre-commit is licensed under either of -Note `v0.0.290` is the minimum version that provides the `ruff-format` hook. +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) -## License +at your option. -MIT +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in ruff-pre-commit by you, as defined in the Apache-2.0 license, shall be +dually licensed as above, without any additional terms or conditions.
diff --git a/mirror.py b/mirror.py new file mode 100644 index 0000000..a1df4cd --- /dev/null +++ b/mirror.py @@ -0,0 +1,84 @@ +# /// script +# requires-python = ">=3.12" +# dependencies = [ +# "packaging==23.1", +# "urllib3==2.0.5", +# ] +# /// +"""Update ruff-pre-commit to the latest version of ruff.""" + +import re +import subprocess +import tomllib +import typing +from pathlib import Path + +import urllib3 +from packaging.requirements import Requirement +from packaging.version import Version + + +def main(): + with open(Path(__file__).parent / "pyproject.toml", "rb") as f: + pyproject = tomllib.load(f) + + all_versions = get_all_versions() + current_version = get_current_version(pyproject=pyproject) + target_versions = [v for v in all_versions if v > current_version] + + for version in target_versions: + paths = process_version(version) + if subprocess.check_output(["git", "status", "-s"]).strip(): + subprocess.run(["git", "add", *paths], check=True) + subprocess.run(["git", "commit", "-m", f"Mirror: {version}"], check=True) + subprocess.run(["git", "tag", f"v{version}"], check=True) + else: + print(f"No change v{version}") + + +def get_all_versions() -> list[Version]: + response = urllib3.request("GET", "https://pypi.org/pypi/ruff/json") + if response.status != 200: + raise RuntimeError("Failed to fetch versions from pypi") + + versions = [Version(release) for release in response.json()["releases"]] + return sorted(versions) + + +def get_current_version(pyproject: dict) -> Version: + requirements = [Requirement(d) for d in pyproject["project"]["dependencies"]] + requirement = next((r for r in requirements if r.name == "ruff"), None) + assert requirement is not None, "pyproject.toml does not have ruff requirement" + + specifiers = list(requirement.specifier) + assert ( + len(specifiers) == 1 and specifiers[0].operator == "==" + ), f"ruff's specifier should be exact matching, but `{requirement}`" + + return Version(specifiers[0].version) + + +def process_version(version: Version) -> typing.Sequence[str]: + def replace_pyproject_toml(content: str) -> str: + return re.sub(r'"ruff==.*"', f'"ruff=={version}"', content) + + def replace_readme_md(content: str) -> str: + content = re.sub(r"rev: v\d+\.\d+\.\d+", f"rev: v{version}", content) + return re.sub(r"/ruff/\d+\.\d+\.\d+\.svg", f"/ruff/{version}.svg", content) + + paths = { + "pyproject.toml": replace_pyproject_toml, + "README.md": replace_readme_md, + } + + for path, replacer in paths.items(): + with open(path) as f: + content = replacer(f.read()) + with open(path, mode="w") as f: + f.write(content) + + return tuple(paths.keys()) + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4468f35 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[project] +name = "ruff-pre-commit" +version = "0.0.0" +dependencies = [ + "ruff==0.15.0", +] diff --git a/setup.py b/setup.py deleted file mode 100644 index 1905625..0000000 --- a/setup.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -from setuptools import setup - - -setup( - name='pre_commit_placeholder_package', - version='0.0.0', - install_requires=['ruff==0.0.290'], -) diff --git a/update_version.py b/update_version.py deleted file mode 100644 index b98868f..0000000 --- a/update_version.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Updates the version in the readme and git commit.""" - -import re -from pathlib import Path -from subprocess import check_call, check_output - - -def main(): - readme_md = Path("README.md") - readme = readme_md.read_text() - rev = Path(".version").read_text().strip() - readme = re.sub(r"rev: v\d+\.\d+\.\d+", f"rev: v{rev}", readme) - readme = re.sub(r"/ruff/\d+\.\d+\.\d+\.svg", f"/ruff/{rev}.svg", readme) - readme_md.write_text(readme) - - # Only commit on change. - # https://stackoverflow.com/a/9393642/3549270 - if check_output(["git", "status", "-s"]).strip(): - check_call(["git", "add", readme_md]) - check_call(["git", "commit", "-m", f"Bump README.md version to {rev}"]) - else: - print("No change") - - -if __name__ == "__main__": - main()