From da1bf27939ea79b7e8dd77cb95fe9fdabd357ff3 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Wed, 6 Nov 2024 20:39:16 +0200 Subject: [PATCH 01/29] feat: check merge base (WIP) --- .gitignore | 1 + commit_check/branch.py | 14 ++++++++++++++ commit_check/util.py | 14 ++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/.gitignore b/.gitignore index 94f592d3..6df705ba 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ __pycache__ .mypy_cache .vscode venv +.venv UNKNOWN.egg-info dist build diff --git a/commit_check/branch.py b/commit_check/branch.py index b1ef5805..7c325a42 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -23,3 +23,17 @@ def check_branch(checks: list) -> int: print_suggestion(check['suggest']) return FAIL return PASS + + +def check_merge_base(checks: list) -> int: + for check in checks: + if check['check'] == 'merge_base': + if check['regex'] == "": + print( + f"{YELLOW}Not found regex for checking merge base. skip checking.{RESET_COLOR}", + ) + return PASS + result = re.match(check['regex'], get_branch_name()) + if result is None: + return FAIL + return PASS \ No newline at end of file diff --git a/commit_check/util.py b/commit_check/util.py index ef8f89ae..f2013cf4 100644 --- a/commit_check/util.py +++ b/commit_check/util.py @@ -51,6 +51,20 @@ def get_commit_info(format_string: str, sha: str = "HEAD") -> str: return output +def check_ancestors(base_branch: str, sha: str) -> bool: + """Check ancestors for a given commit. + :param base_branch: base branch + :param sha: commit hash. default is HEAD + + :returns: Get 0 if there is ancestor else 1. + """ + try: + commands = ['git', 'merge-base', f'{base_branch}^..{sha}'] + output = cmd_output(commands) + except CalledProcessError: + output = '' + return output.split() + def cmd_output(commands: list) -> str: """Run command :param commands: list of commands From 4a4684818c377b857378c4e23c94d850434240f5 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Thu, 7 Nov 2024 19:34:59 +0200 Subject: [PATCH 02/29] feat: check merge base (WIP) --- .commit-check.yml | 5 +++++ commit_check/__init__.py | 6 ++++++ commit_check/branch.py | 6 +++--- commit_check/main.py | 10 ++++++++++ commit_check/util.py | 14 ++++++++------ 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/.commit-check.yml b/.commit-check.yml index 39d657c9..c13dc006 100644 --- a/.commit-check.yml +++ b/.commit-check.yml @@ -27,3 +27,8 @@ checks: regex: Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+> error: Signed-off-by not found in latest commit suggest: run command `git commit -m "conventional commit message" --signoff` + + - check: merge_base + regex: main # target branch + error: No merge base found between HEAD and target branch + suggest: run command `git merge-base main HEAD` diff --git a/commit_check/__init__.py b/commit_check/__init__.py index a88235cd..4227102b 100644 --- a/commit_check/__init__.py +++ b/commit_check/__init__.py @@ -48,6 +48,12 @@ 'error': 'Signed-off-by not found in latest commit', 'suggest': 'run command `git commit -m "conventional commit message" --signoff`', }, + { + 'check': 'merge_base', + 'regex': 'main', # target branch + 'error': 'No merge base found between HEAD and target branch', + 'suggest': 'run command `git merge-base main HEAD`', + }, ], } diff --git a/commit_check/branch.py b/commit_check/branch.py index 7c325a42..3faeeaea 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -1,7 +1,7 @@ """Check git branch naming convention.""" import re from commit_check import YELLOW, RESET_COLOR, PASS, FAIL -from commit_check.util import get_branch_name, print_error_message, print_suggestion +from commit_check.util import get_branch_name, git_merge_base, print_error_message, print_suggestion def check_branch(checks: list) -> int: @@ -30,10 +30,10 @@ def check_merge_base(checks: list) -> int: if check['check'] == 'merge_base': if check['regex'] == "": print( - f"{YELLOW}Not found regex for checking merge base. skip checking.{RESET_COLOR}", + f"{YELLOW}Not found target branch for checking merge base. skip checking.{RESET_COLOR}", ) return PASS - result = re.match(check['regex'], get_branch_name()) + result = git_merge_base(check['regex'], 'HEAD') if result is None: return FAIL return PASS \ No newline at end of file diff --git a/commit_check/main.py b/commit_check/main.py index 0a8a020b..3a2e6f3f 100644 --- a/commit_check/main.py +++ b/commit_check/main.py @@ -76,6 +76,14 @@ def get_parser() -> argparse.ArgumentParser: required=False, ) + parser.add_argument( + '-mb', + '--merge-base', + help='check common ancestors', + action="store_true", + required=False, + ) + parser.add_argument( '-d', '--dry-run', @@ -108,6 +116,8 @@ def main() -> int: retval = branch.check_branch(checks) if args.commit_signoff: retval = commit.check_commit_signoff(checks) + if args.merge_base: + retval = branch.check_merge_base(checks) if args.dry_run: retval = PASS diff --git a/commit_check/util.py b/commit_check/util.py index f2013cf4..76ceba89 100644 --- a/commit_check/util.py +++ b/commit_check/util.py @@ -51,19 +51,21 @@ def get_commit_info(format_string: str, sha: str = "HEAD") -> str: return output -def check_ancestors(base_branch: str, sha: str) -> bool: +def git_merge_base(target_branch: str, sha: str) -> bool: """Check ancestors for a given commit. - :param base_branch: base branch + :param target_branch: target branch :param sha: commit hash. default is HEAD :returns: Get 0 if there is ancestor else 1. """ try: - commands = ['git', 'merge-base', f'{base_branch}^..{sha}'] - output = cmd_output(commands) + commands = ['git', 'merge-base', '--is-ancestor', f'origin/{target_branch}', f'{sha}'] + result = subprocess.run( + commands, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8' + ) + return result.returncode except CalledProcessError: - output = '' - return output.split() + return 1 def cmd_output(commands: list) -> str: """Run command From f7aacc1e1918dc3f8fb646cddf39b26870cd293f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:39:26 +0000 Subject: [PATCH 03/29] ci: auto fixes from pre-commit.com hooks --- commit_check/branch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commit_check/branch.py b/commit_check/branch.py index 3faeeaea..863f61e0 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -36,4 +36,4 @@ def check_merge_base(checks: list) -> int: result = git_merge_base(check['regex'], 'HEAD') if result is None: return FAIL - return PASS \ No newline at end of file + return PASS From ec2c79141c7d5442135f282132af824a5b5067fd Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Thu, 7 Nov 2024 19:44:05 +0200 Subject: [PATCH 04/29] fix pre-commit check issues --- commit_check/branch.py | 2 +- commit_check/util.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/commit_check/branch.py b/commit_check/branch.py index 863f61e0..56bb98b4 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -34,6 +34,6 @@ def check_merge_base(checks: list) -> int: ) return PASS result = git_merge_base(check['regex'], 'HEAD') - if result is None: + if result == 1: return FAIL return PASS diff --git a/commit_check/util.py b/commit_check/util.py index 76ceba89..c791526a 100644 --- a/commit_check/util.py +++ b/commit_check/util.py @@ -51,7 +51,7 @@ def get_commit_info(format_string: str, sha: str = "HEAD") -> str: return output -def git_merge_base(target_branch: str, sha: str) -> bool: +def git_merge_base(target_branch: str, sha: str) -> int: """Check ancestors for a given commit. :param target_branch: target branch :param sha: commit hash. default is HEAD @@ -67,6 +67,7 @@ def git_merge_base(target_branch: str, sha: str) -> bool: except CalledProcessError: return 1 + def cmd_output(commands: list) -> str: """Run command :param commands: list of commands From 00da81664936d2996bbed8a92334f8cf4dd940a5 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Fri, 8 Nov 2024 10:30:20 +0200 Subject: [PATCH 05/29] Update commit_check/branch.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- commit_check/branch.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/commit_check/branch.py b/commit_check/branch.py index 56bb98b4..bd36d30a 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -26,6 +26,14 @@ def check_branch(checks: list) -> int: def check_merge_base(checks: list) -> int: + """Check if the current branch is based on the latest target branch. + + Args: + checks: List of check configurations containing merge_base rules + + Returns: + PASS if merge base check succeeds, FAIL otherwise + """ for check in checks: if check['check'] == 'merge_base': if check['regex'] == "": @@ -35,5 +43,13 @@ def check_merge_base(checks: list) -> int: return PASS result = git_merge_base(check['regex'], 'HEAD') if result == 1: + print_error_message( + check['check'], + check['regex'], + f"Branch is not up to date with {check['regex']}", + 'HEAD' + ) + if check.get('suggest'): + print_suggestion(f"Run 'git rebase {check['regex']}' to update your branch") return FAIL return PASS From b6d0e250e32c3e32af1d713cc62268e6d0344fe3 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Fri, 8 Nov 2024 10:30:56 +0200 Subject: [PATCH 06/29] Update .commit-check.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .commit-check.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.commit-check.yml b/.commit-check.yml index c13dc006..cb5bc6c2 100644 --- a/.commit-check.yml +++ b/.commit-check.yml @@ -29,6 +29,11 @@ checks: suggest: run command `git commit -m "conventional commit message" --signoff` - check: merge_base - regex: main # target branch + regex: ${TARGET_BRANCH:-main} # configurable target branch, defaults to main error: No merge base found between HEAD and target branch - suggest: run command `git merge-base main HEAD` + suggest: | + Please ensure your branch is up to date with the target branch by running: + git fetch origin ${TARGET_BRANCH:-main} + git rebase origin/${TARGET_BRANCH:-main} + # If you encounter conflicts, resolve them and continue with: + git rebase --continue From ceccc4c42417d03fcaf9679438d180c5354b6aa5 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Fri, 8 Nov 2024 14:07:42 +0000 Subject: [PATCH 07/29] feat: add noxfile.py --- .github/workflows/main.yml | 33 ++++++++-------------- .gitignore | 2 ++ noxfile.py | 56 ++++++++++++++++++++++++++++++++++++++ requirements-dev.txt | 1 + tests/util_test.py | 10 +++++++ 5 files changed, 81 insertions(+), 21 deletions(-) create mode 100644 noxfile.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3737c30d..8f6801aa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,34 +21,29 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.x' - - - name: Install dependencies - run: | - pip install -r requirements-dev.txt - pip install -e . + # TODO: waiting + - run: pip install git+https://github.com/wntrblm/nox.git@main --force-reinstall - name: Run pre-commit run: | - pre-commit run --all-files - pre-commit try-repo . + nox -s lint + nox -s test-hook - name: Build wheel - run: python3 -m pip wheel --no-deps -w dist . + run: nox -s build + - name: Upload wheel as artifact uses: actions/upload-artifact@v4 with: name: commit-check_wheel path: ${{ github.workspace }}/dist/*.whl + - name: Run commit-check - run: | - python3 -m pip install dist/*.whl - commit-check -h - commit-check --message --branch --author-email + run: nox -s commit-check - name: Collect Coverage - run: | - coverage run --source commit_check -m pytest - coverage report && coverage xml + run: nox -s coverage + - uses: codecov/codecov-action@v4.6.0 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -77,9 +72,7 @@ jobs: path: dist - name: Install test - # using a wildcard as filename on Windows requires a bash shell - shell: bash - run: python3 -m pip install dist/*.whl + run: nox -s install-wheel docs: runs-on: ubuntu-latest @@ -88,11 +81,9 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.10" - - run: python -m pip install . -r docs/requirements.txt - name: Build docs - working-directory: docs - run: sphinx-build -E -W -b html . _build/html + run: nox -s docs - name: Save built docs as artifact uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index 6df705ba..6e9ea54f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ build tests/__pycache__ .coverage coverage.xml +.nox +_build/ # docs docs/_build diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 00000000..a62ab72c --- /dev/null +++ b/noxfile.py @@ -0,0 +1,56 @@ +import nox +from pathlib import Path + +nox.options.reuse_existing_virtualenvs = True +nox.options.sessions = ["lint"] + +REQUIREMENTS = { + "dev": "requirements-dev.txt", + "docs": "docs/requirements.txt", +} + +# ----------------------------------------------------------------------------- +# Development Commands +# ----------------------------------------------------------------------------- + +@nox.session() +def lint(session): + session.install("pre-commit") + if session.posargs: + args = session.posargs + ["--all-files"] + else: + args = ["--all-files", "--show-diff-on-failure"] + + session.run("pre-commit", "run", *args) + +@nox.session(name="test-hook") +def test_hook(session): + session.install("-e", ".") + session.install("pre-commit") + session.run("pre-commit", "try-repo", ".") + +@nox.session() +def build(session): + session.run("python3", "-m", "pip", "wheel", "--no-deps", "-w", "dist", ".") + +@nox.session(name="install-wheel") +def install_wheel(session): + session.install(str(*Path("dist").glob("*.whl"))) + +# @nox.session(name="commit-check", requires=["install-wheel"]) +@nox.session(name="commit-check", requires=["install-wheel"]) +def commit_check(session): + session.run("commit-check", "-h") + session.run("commit-check", "--message", "--branch", "--author-email") + +@nox.session(requires=["install-wheel"]) +def coverage(session): + session.install("coverage", "run", "--source", "commit_check", "-m", "pytest") + session.install("coverage", "report") + session.install("coverage", "xml") + +@nox.session() +def docs(session): + session.install("-e", ".") + session.install("-r", REQUIREMENTS["docs"]) + session.run("sphinx-build", "-E", "-W", "-b", "html", "docs", "_build/html") diff --git a/requirements-dev.txt b/requirements-dev.txt index 787fd29d..cb4cde73 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,5 @@ coverage +nox pre-commit pytest pytest-mock diff --git a/tests/util_test.py b/tests/util_test.py index 70ae5b7e..21a804b4 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -1,6 +1,7 @@ import pytest from commit_check.util import get_branch_name from commit_check.util import get_commit_info +from commit_check.util import git_merge_base from commit_check.util import cmd_output from commit_check.util import validate_config from commit_check.util import print_error_message @@ -210,3 +211,12 @@ def test_print_suggestion_exit1(self, capfd): assert e.value.code == 1 stdout, _ = capfd.readouterr() assert "commit-check does not support" in stdout + + class TestGitMergeBase: + def test_successful_ancestor_check(self, mocker): + m_cmd_output = mocker.patch( + "commit_check.util.git_merge_base", + return_value=0 + ) + retval = git_merge_base("main", "HEAD") + assert retval == 0 From 432434ed392deccfaf6d4dfb5beb5abeb44cae13 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:09:34 +0000 Subject: [PATCH 08/29] ci: auto fixes from pre-commit.com hooks --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8f6801aa..466b9222 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.x' - # TODO: waiting + # TODO: waiting - run: pip install git+https://github.com/wntrblm/nox.git@main --force-reinstall - name: Run pre-commit From be6d753e49d8d95465153b8882670ab8db516bc7 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Fri, 8 Nov 2024 14:13:26 +0000 Subject: [PATCH 09/29] fix pre-commit check --- .github/workflows/main.yml | 2 +- noxfile.py | 7 +++++++ tests/util_test.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 466b9222..73d9e489 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.x' - # TODO: waiting + # TODO: waiting for https://github.com/wntrblm/nox/pull/631 to deliver - run: pip install git+https://github.com/wntrblm/nox.git@main --force-reinstall - name: Run pre-commit diff --git a/noxfile.py b/noxfile.py index a62ab72c..3bc5d054 100644 --- a/noxfile.py +++ b/noxfile.py @@ -13,6 +13,7 @@ # Development Commands # ----------------------------------------------------------------------------- + @nox.session() def lint(session): session.install("pre-commit") @@ -23,32 +24,38 @@ def lint(session): session.run("pre-commit", "run", *args) + @nox.session(name="test-hook") def test_hook(session): session.install("-e", ".") session.install("pre-commit") session.run("pre-commit", "try-repo", ".") + @nox.session() def build(session): session.run("python3", "-m", "pip", "wheel", "--no-deps", "-w", "dist", ".") + @nox.session(name="install-wheel") def install_wheel(session): session.install(str(*Path("dist").glob("*.whl"))) + # @nox.session(name="commit-check", requires=["install-wheel"]) @nox.session(name="commit-check", requires=["install-wheel"]) def commit_check(session): session.run("commit-check", "-h") session.run("commit-check", "--message", "--branch", "--author-email") + @nox.session(requires=["install-wheel"]) def coverage(session): session.install("coverage", "run", "--source", "commit_check", "-m", "pytest") session.install("coverage", "report") session.install("coverage", "xml") + @nox.session() def docs(session): session.install("-e", ".") diff --git a/tests/util_test.py b/tests/util_test.py index 21a804b4..e01f4d9d 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -219,4 +219,4 @@ def test_successful_ancestor_check(self, mocker): return_value=0 ) retval = git_merge_base("main", "HEAD") - assert retval == 0 + assert retval == m_cmd_output.returncode From aacf5b11ae0649608440f57cee8c6d29703ae1d6 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Fri, 8 Nov 2024 16:42:02 +0200 Subject: [PATCH 10/29] Update noxfile.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- noxfile.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index 3bc5d054..6a369822 100644 --- a/noxfile.py +++ b/noxfile.py @@ -45,8 +45,13 @@ def install_wheel(session): # @nox.session(name="commit-check", requires=["install-wheel"]) @nox.session(name="commit-check", requires=["install-wheel"]) def commit_check(session): - session.run("commit-check", "-h") - session.run("commit-check", "--message", "--branch", "--author-email") + session.run( + "commit-check", + "--message", + "--branch", + "--author-email", + "--merge-base", + ) @nox.session(requires=["install-wheel"]) From fd7deb8259167b6e4332a9ba6360626447c1aaaf Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Fri, 8 Nov 2024 18:59:43 +0000 Subject: [PATCH 11/29] fix: update merge_base feature --- .commit-check.yml | 8 ++++---- commit_check/__init__.py | 2 +- commit_check/branch.py | 17 ++++++----------- commit_check/util.py | 4 ++-- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/.commit-check.yml b/.commit-check.yml index cb5bc6c2..31e80975 100644 --- a/.commit-check.yml +++ b/.commit-check.yml @@ -29,11 +29,11 @@ checks: suggest: run command `git commit -m "conventional commit message" --signoff` - check: merge_base - regex: ${TARGET_BRANCH:-main} # configurable target branch, defaults to main - error: No merge base found between HEAD and target branch + regex: main # configurable target branch, defaults to main + error: Current branch is not up to date with main suggest: | Please ensure your branch is up to date with the target branch by running: - git fetch origin ${TARGET_BRANCH:-main} - git rebase origin/${TARGET_BRANCH:-main} + git fetch origin main + git rebase origin/main # If you encounter conflicts, resolve them and continue with: git rebase --continue diff --git a/commit_check/__init__.py b/commit_check/__init__.py index 4227102b..18582c32 100644 --- a/commit_check/__init__.py +++ b/commit_check/__init__.py @@ -51,7 +51,7 @@ { 'check': 'merge_base', 'regex': 'main', # target branch - 'error': 'No merge base found between HEAD and target branch', + 'error': 'Current branch is not up to date with main', 'suggest': 'run command `git merge-base main HEAD`', }, ], diff --git a/commit_check/branch.py b/commit_check/branch.py index bd36d30a..d1c170d8 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -27,12 +27,9 @@ def check_branch(checks: list) -> int: def check_merge_base(checks: list) -> int: """Check if the current branch is based on the latest target branch. + params checks: List of check configurations containing merge_base rules - Args: - checks: List of check configurations containing merge_base rules - - Returns: - PASS if merge base check succeeds, FAIL otherwise + :returns PASS(0) if merge base check succeeds, FAIL(1) otherwise """ for check in checks: if check['check'] == 'merge_base': @@ -40,14 +37,12 @@ def check_merge_base(checks: list) -> int: print( f"{YELLOW}Not found target branch for checking merge base. skip checking.{RESET_COLOR}", ) - return PASS result = git_merge_base(check['regex'], 'HEAD') - if result == 1: + if result != 0: + branch_name = get_branch_name() print_error_message( - check['check'], - check['regex'], - f"Branch is not up to date with {check['regex']}", - 'HEAD' + check['check'], check['regex'], + check['error'], branch_name, ) if check.get('suggest'): print_suggestion(f"Run 'git rebase {check['regex']}' to update your branch") diff --git a/commit_check/util.py b/commit_check/util.py index c791526a..c7dbbfb3 100644 --- a/commit_check/util.py +++ b/commit_check/util.py @@ -59,13 +59,13 @@ def git_merge_base(target_branch: str, sha: str) -> int: :returns: Get 0 if there is ancestor else 1. """ try: - commands = ['git', 'merge-base', '--is-ancestor', f'origin/{target_branch}', f'{sha}'] + commands = ['git', 'merge-base', '--is-ancestor', f'{target_branch}', f'{sha}'] result = subprocess.run( commands, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8' ) return result.returncode except CalledProcessError: - return 1 + return 128 def cmd_output(commands: list) -> str: From 9cdecd4280827f8224dbbf560fe13cc88707d012 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Fri, 8 Nov 2024 19:36:56 +0000 Subject: [PATCH 12/29] feat: refactor print error message --- commit_check/author.py | 4 +++- commit_check/branch.py | 6 +++++- commit_check/commit.py | 6 +++++- commit_check/util.py | 30 ++++++++++++++++++++++-------- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/commit_check/author.py b/commit_check/author.py index 1ab3ba13..2d9bf594 100644 --- a/commit_check/author.py +++ b/commit_check/author.py @@ -1,7 +1,7 @@ """Check git author name and email""" import re from commit_check import YELLOW, RESET_COLOR, PASS, FAIL -from commit_check.util import get_commit_info, print_error_message, print_suggestion +from commit_check.util import get_commit_info, print_error_head, print_error_message, print_suggestion def check_author(checks: list, check_type: str) -> int: @@ -19,6 +19,8 @@ def check_author(checks: list, check_type: str) -> int: config_value = str(get_commit_info(format_str)) result = re.match(check['regex'], config_value) if result is None: + if not print_error_head.has_been_called: + print_error_head() print_error_message( check['check'], check['regex'], check['error'], config_value, diff --git a/commit_check/branch.py b/commit_check/branch.py index d1c170d8..74ff50d2 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -1,7 +1,7 @@ """Check git branch naming convention.""" import re from commit_check import YELLOW, RESET_COLOR, PASS, FAIL -from commit_check.util import get_branch_name, git_merge_base, print_error_message, print_suggestion +from commit_check.util import get_branch_name, git_merge_base, print_error_head, print_error_message, print_suggestion def check_branch(checks: list) -> int: @@ -15,6 +15,8 @@ def check_branch(checks: list) -> int: branch_name = get_branch_name() result = re.match(check['regex'], branch_name) if result is None: + if not print_error_head.has_been_called: + print_error_head() print_error_message( check['check'], check['regex'], check['error'], branch_name, @@ -40,6 +42,8 @@ def check_merge_base(checks: list) -> int: result = git_merge_base(check['regex'], 'HEAD') if result != 0: branch_name = get_branch_name() + if not print_error_head.has_been_called: + print_error_head() print_error_message( check['check'], check['regex'], check['error'], branch_name, diff --git a/commit_check/commit.py b/commit_check/commit.py index 30b4dfe5..286e770a 100644 --- a/commit_check/commit.py +++ b/commit_check/commit.py @@ -2,7 +2,7 @@ import re from pathlib import PurePath from commit_check import YELLOW, RESET_COLOR, PASS, FAIL -from commit_check.util import cmd_output, get_commit_info, print_error_message, print_suggestion +from commit_check.util import cmd_output, get_commit_info, print_error_head, print_error_message, print_suggestion def get_default_commit_msg_file() -> str: @@ -37,6 +37,8 @@ def check_commit_msg(checks: list, commit_msg_file: str = "") -> int: if check['check'] == 'message': result = re.match(check['regex'], commit_msg) if result is None: + if not print_error_head.has_been_called: + print_error_head() print_error_message( check['check'], check['regex'], check['error'], commit_msg, @@ -64,6 +66,8 @@ def check_commit_signoff(checks: list, commit_msg_file: str = "") -> int: commit_hash = get_commit_info("H") result = re.search(check['regex'], commit_msg) if result is None: + if not print_error_head.has_been_called: + print_error_head() print_error_message( check['check'], check['regex'], check['error'], commit_hash, diff --git a/commit_check/util.py b/commit_check/util.py index c7dbbfb3..55ac5478 100644 --- a/commit_check/util.py +++ b/commit_check/util.py @@ -100,14 +100,18 @@ def validate_config(path_to_config: str) -> dict: return configuration -def print_error_message(check_type: str, regex: str, error: str, reason: str): - """Print error message. - :param check_type: - :param regex: - :param error: - :param reason: +def track_print_call(func): + def wrapper(*args, **kwargs): + wrapper.has_been_called = True + return func(*args, **kwargs) + wrapper.has_been_called = False # Initialize as False + return wrapper - :returns: Give error messages to user + +@track_print_call +def print_error_head(): + """Print error message. + :returns: Print error head to user """ print("Commit rejected by Commit-Check. ") print(" ") @@ -122,10 +126,20 @@ def print_error_message(check_type: str, regex: str, error: str, reason: str): print(" ") print("Commit rejected. ") print(" ") + + +def print_error_message(check_type: str, regex: str, error: str, reason: str): + """Print error message. + :param check_type: + :param regex: + :param error: + :param reason: + + :returns: Give error messages to user + """ print(f"Type {YELLOW}{check_type}{RESET_COLOR} check failed => {RED}{reason}{RESET_COLOR} ", end='',) print("") print(f"It doesn't match regex: {regex}") - print("") print(error) From 5fb9ac065bc847a8fac959f32a0891d061ff6eea Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Fri, 8 Nov 2024 22:51:52 +0200 Subject: [PATCH 13/29] fix: update noxfile.py to fix workflow --- .github/workflows/main.yml | 1 + noxfile.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 73d9e489..851e50c6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,6 +64,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: ${{ matrix.py }} + - run: pip install git+https://github.com/wntrblm/nox.git@main --force-reinstall - name: Download wheel artifact uses: actions/download-artifact@v4 diff --git a/noxfile.py b/noxfile.py index 6a369822..54a38be1 100644 --- a/noxfile.py +++ b/noxfile.py @@ -37,7 +37,7 @@ def build(session): session.run("python3", "-m", "pip", "wheel", "--no-deps", "-w", "dist", ".") -@nox.session(name="install-wheel") +@nox.session(name="install-wheel", requires=["build"]) def install_wheel(session): session.install(str(*Path("dist").glob("*.whl"))) From a743a2b62394a53635730a002ea2578292edb42c Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Fri, 8 Nov 2024 22:53:43 +0200 Subject: [PATCH 14/29] fix: update noxfile.py to fix workflow --- noxfile.py | 1 - 1 file changed, 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 54a38be1..c4ab672b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -50,7 +50,6 @@ def commit_check(session): "--message", "--branch", "--author-email", - "--merge-base", ) From 5fe316771b2cbb0d2c6d223c918df54294676123 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Fri, 8 Nov 2024 23:01:06 +0200 Subject: [PATCH 15/29] fix: update noxfile.py to fix finding wheel --- noxfile.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index c4ab672b..7fd768ef 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,5 +1,5 @@ import nox -from pathlib import Path +import glob nox.options.reuse_existing_virtualenvs = True nox.options.sessions = ["lint"] @@ -39,7 +39,8 @@ def build(session): @nox.session(name="install-wheel", requires=["build"]) def install_wheel(session): - session.install(str(*Path("dist").glob("*.whl"))) + whl_file = glob.glob("dist/*.whl") + session.install(str(whl_file[0])) # @nox.session(name="commit-check", requires=["install-wheel"]) From 30761bb834bed321dddd0e2ffc69f2681afe9f1b Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sat, 9 Nov 2024 09:37:45 +0200 Subject: [PATCH 16/29] fix: update noxfile.py --- noxfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/noxfile.py b/noxfile.py index 7fd768ef..a1e386a4 100644 --- a/noxfile.py +++ b/noxfile.py @@ -2,6 +2,7 @@ import glob nox.options.reuse_existing_virtualenvs = True +nox.options.reuse_venv = True nox.options.sessions = ["lint"] REQUIREMENTS = { From f196cb928f4add760c63fe42ad3d05b37932145f Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sat, 9 Nov 2024 09:53:14 +0200 Subject: [PATCH 17/29] test: disable run commit-check --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 851e50c6..3b4f793a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,8 +38,8 @@ jobs: name: commit-check_wheel path: ${{ github.workspace }}/dist/*.whl - - name: Run commit-check - run: nox -s commit-check + # - name: Run commit-check + # run: nox -s commit-check - name: Collect Coverage run: nox -s coverage From b3fd87bcc19cdea5870f72abef7bf08a8fdacb7d Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sat, 9 Nov 2024 10:01:00 +0200 Subject: [PATCH 18/29] fix: revert main.yml --- .github/workflows/main.yml | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3b4f793a..141c1e4d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,29 +21,34 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.x' - # TODO: waiting for https://github.com/wntrblm/nox/pull/631 to deliver - - run: pip install git+https://github.com/wntrblm/nox.git@main --force-reinstall + + - name: Install dependencies + run: | + pip install -r requirements-dev.txt + pip install -e . - name: Run pre-commit run: | - nox -s lint - nox -s test-hook + pre-commit run --all-files + pre-commit try-repo . - name: Build wheel - run: nox -s build - + run: python3 -m pip wheel --no-deps -w dist . - name: Upload wheel as artifact uses: actions/upload-artifact@v4 with: name: commit-check_wheel path: ${{ github.workspace }}/dist/*.whl - - # - name: Run commit-check - # run: nox -s commit-check + - name: Run commit-check + run: | + python3 -m pip install dist/*.whl + commit-check -h + commit-check --message --branch --author-email - name: Collect Coverage - run: nox -s coverage - + run: | + coverage run --source commit_check -m pytest + coverage report && coverage xml - uses: codecov/codecov-action@v4.6.0 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -64,7 +69,6 @@ jobs: - uses: actions/setup-python@v5 with: python-version: ${{ matrix.py }} - - run: pip install git+https://github.com/wntrblm/nox.git@main --force-reinstall - name: Download wheel artifact uses: actions/download-artifact@v4 @@ -73,7 +77,9 @@ jobs: path: dist - name: Install test - run: nox -s install-wheel + # using a wildcard as filename on Windows requires a bash shell + shell: bash + run: python3 -m pip install dist/*.whl docs: runs-on: ubuntu-latest @@ -82,9 +88,11 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.10" + - run: python -m pip install . -r docs/requirements.txt - name: Build docs - run: nox -s docs + working-directory: docs + run: sphinx-build -E -W -b html . _build/html - name: Save built docs as artifact uses: actions/upload-artifact@v4 @@ -98,4 +106,4 @@ jobs: uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/_build/html + publish_dir: ./docs/_build/html \ No newline at end of file From 1c5df69485058e04c5138071ca1da5eb9f7764e8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 9 Nov 2024 08:01:06 +0000 Subject: [PATCH 19/29] ci: auto fixes from pre-commit.com hooks --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 141c1e4d..3737c30d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -106,4 +106,4 @@ jobs: uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/_build/html \ No newline at end of file + publish_dir: ./docs/_build/html From 4b53208cbe4add33965db9d49951e8c285763a3d Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sat, 9 Nov 2024 21:51:27 +0200 Subject: [PATCH 20/29] fix: removed does work test case --- commit_check/util.py | 6 +++--- tests/util_test.py | 10 ---------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/commit_check/util.py b/commit_check/util.py index 55ac5478..7d55b647 100644 --- a/commit_check/util.py +++ b/commit_check/util.py @@ -51,15 +51,15 @@ def get_commit_info(format_string: str, sha: str = "HEAD") -> str: return output -def git_merge_base(target_branch: str, sha: str) -> int: +def git_merge_base(target_branch: str, current_branch: str) -> int: """Check ancestors for a given commit. :param target_branch: target branch - :param sha: commit hash. default is HEAD + :param current_branch: default is HEAD :returns: Get 0 if there is ancestor else 1. """ try: - commands = ['git', 'merge-base', '--is-ancestor', f'{target_branch}', f'{sha}'] + commands = ['git', 'merge-base', '--is-ancestor', f'{target_branch}', f'{current_branch}'] result = subprocess.run( commands, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8' ) diff --git a/tests/util_test.py b/tests/util_test.py index e01f4d9d..70ae5b7e 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -1,7 +1,6 @@ import pytest from commit_check.util import get_branch_name from commit_check.util import get_commit_info -from commit_check.util import git_merge_base from commit_check.util import cmd_output from commit_check.util import validate_config from commit_check.util import print_error_message @@ -211,12 +210,3 @@ def test_print_suggestion_exit1(self, capfd): assert e.value.code == 1 stdout, _ = capfd.readouterr() assert "commit-check does not support" in stdout - - class TestGitMergeBase: - def test_successful_ancestor_check(self, mocker): - m_cmd_output = mocker.patch( - "commit_check.util.git_merge_base", - return_value=0 - ) - retval = git_merge_base("main", "HEAD") - assert retval == m_cmd_output.returncode From d07ae51ea3352eaae53b70371677588cba67f7bd Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sat, 9 Nov 2024 21:56:16 +0200 Subject: [PATCH 21/29] fix: update merge_base regex --- .commit-check.yml | 2 +- commit_check/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.commit-check.yml b/.commit-check.yml index 31e80975..434810b8 100644 --- a/.commit-check.yml +++ b/.commit-check.yml @@ -29,7 +29,7 @@ checks: suggest: run command `git commit -m "conventional commit message" --signoff` - check: merge_base - regex: main # configurable target branch, defaults to main + regex: (main|master|develop|devel) error: Current branch is not up to date with main suggest: | Please ensure your branch is up to date with the target branch by running: diff --git a/commit_check/__init__.py b/commit_check/__init__.py index 18582c32..6f9b1c22 100644 --- a/commit_check/__init__.py +++ b/commit_check/__init__.py @@ -50,7 +50,7 @@ }, { 'check': 'merge_base', - 'regex': 'main', # target branch + 'regex': r'(main|master|develop|devel)', 'error': 'Current branch is not up to date with main', 'suggest': 'run command `git merge-base main HEAD`', }, From 26438eecfdcc47f0ed67c0aa13857c88c2c7e615 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sat, 9 Nov 2024 22:09:10 +0200 Subject: [PATCH 22/29] fix: refactor code based on review --- .commit-check.yml | 2 +- commit_check/__init__.py | 2 +- commit_check/branch.py | 1 + commit_check/util.py | 2 +- noxfile.py | 6 +++--- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.commit-check.yml b/.commit-check.yml index 434810b8..29c34f49 100644 --- a/.commit-check.yml +++ b/.commit-check.yml @@ -30,7 +30,7 @@ checks: - check: merge_base regex: (main|master|develop|devel) - error: Current branch is not up to date with main + error: Current branch is not up to date with target branch suggest: | Please ensure your branch is up to date with the target branch by running: git fetch origin main diff --git a/commit_check/__init__.py b/commit_check/__init__.py index 6f9b1c22..34541d9a 100644 --- a/commit_check/__init__.py +++ b/commit_check/__init__.py @@ -51,7 +51,7 @@ { 'check': 'merge_base', 'regex': r'(main|master|develop|devel)', - 'error': 'Current branch is not up to date with main', + 'error': 'Current branch is not up to date with target branch', 'suggest': 'run command `git merge-base main HEAD`', }, ], diff --git a/commit_check/branch.py b/commit_check/branch.py index 74ff50d2..f9341007 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -39,6 +39,7 @@ def check_merge_base(checks: list) -> int: print( f"{YELLOW}Not found target branch for checking merge base. skip checking.{RESET_COLOR}", ) + return PASS result = git_merge_base(check['regex'], 'HEAD') if result != 0: branch_name = get_branch_name() diff --git a/commit_check/util.py b/commit_check/util.py index 7d55b647..1774ae6b 100644 --- a/commit_check/util.py +++ b/commit_check/util.py @@ -56,7 +56,7 @@ def git_merge_base(target_branch: str, current_branch: str) -> int: :param target_branch: target branch :param current_branch: default is HEAD - :returns: Get 0 if there is ancestor else 1. + :returns: 0 if ancestor exists, 1 if not, 128 if git command fails. """ try: commands = ['git', 'merge-base', '--is-ancestor', f'{target_branch}', f'{current_branch}'] diff --git a/noxfile.py b/noxfile.py index a1e386a4..e10a4f78 100644 --- a/noxfile.py +++ b/noxfile.py @@ -57,9 +57,9 @@ def commit_check(session): @nox.session(requires=["install-wheel"]) def coverage(session): - session.install("coverage", "run", "--source", "commit_check", "-m", "pytest") - session.install("coverage", "report") - session.install("coverage", "xml") + session.run("coverage", "run", "--source", "commit_check", "-m", "pytest") + session.run("coverage", "report") + session.run("coverage", "xml") @nox.session() From 5b30018c72716d33348a99ca051edf2aca36e2f0 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 10 Nov 2024 10:13:21 +0200 Subject: [PATCH 23/29] fix: update noxfile.py to fix lint --- commit_check/author.py | 6 +++--- commit_check/branch.py | 10 +++++----- commit_check/commit.py | 10 +++++----- commit_check/util.py | 2 +- noxfile.py | 2 ++ tests/util_test.py | 10 ++++++++-- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/commit_check/author.py b/commit_check/author.py index 2d9bf594..a68397e3 100644 --- a/commit_check/author.py +++ b/commit_check/author.py @@ -1,7 +1,7 @@ """Check git author name and email""" import re from commit_check import YELLOW, RESET_COLOR, PASS, FAIL -from commit_check.util import get_commit_info, print_error_head, print_error_message, print_suggestion +from commit_check.util import get_commit_info, print_error_header, print_error_message, print_suggestion def check_author(checks: list, check_type: str) -> int: @@ -19,8 +19,8 @@ def check_author(checks: list, check_type: str) -> int: config_value = str(get_commit_info(format_str)) result = re.match(check['regex'], config_value) if result is None: - if not print_error_head.has_been_called: - print_error_head() + if not print_error_header.has_been_called: + print_error_header() print_error_message( check['check'], check['regex'], check['error'], config_value, diff --git a/commit_check/branch.py b/commit_check/branch.py index f9341007..2823aca9 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -1,7 +1,7 @@ """Check git branch naming convention.""" import re from commit_check import YELLOW, RESET_COLOR, PASS, FAIL -from commit_check.util import get_branch_name, git_merge_base, print_error_head, print_error_message, print_suggestion +from commit_check.util import get_branch_name, git_merge_base, print_error_header, print_error_message, print_suggestion def check_branch(checks: list) -> int: @@ -15,8 +15,8 @@ def check_branch(checks: list) -> int: branch_name = get_branch_name() result = re.match(check['regex'], branch_name) if result is None: - if not print_error_head.has_been_called: - print_error_head() + if not print_error_header.has_been_called: + print_error_header() print_error_message( check['check'], check['regex'], check['error'], branch_name, @@ -43,8 +43,8 @@ def check_merge_base(checks: list) -> int: result = git_merge_base(check['regex'], 'HEAD') if result != 0: branch_name = get_branch_name() - if not print_error_head.has_been_called: - print_error_head() + if not print_error_header.has_been_called: + print_error_header() print_error_message( check['check'], check['regex'], check['error'], branch_name, diff --git a/commit_check/commit.py b/commit_check/commit.py index 286e770a..5505e770 100644 --- a/commit_check/commit.py +++ b/commit_check/commit.py @@ -2,7 +2,7 @@ import re from pathlib import PurePath from commit_check import YELLOW, RESET_COLOR, PASS, FAIL -from commit_check.util import cmd_output, get_commit_info, print_error_head, print_error_message, print_suggestion +from commit_check.util import cmd_output, get_commit_info, print_error_header, print_error_message, print_suggestion def get_default_commit_msg_file() -> str: @@ -37,8 +37,8 @@ def check_commit_msg(checks: list, commit_msg_file: str = "") -> int: if check['check'] == 'message': result = re.match(check['regex'], commit_msg) if result is None: - if not print_error_head.has_been_called: - print_error_head() + if not print_error_header.has_been_called: + print_error_header() print_error_message( check['check'], check['regex'], check['error'], commit_msg, @@ -66,8 +66,8 @@ def check_commit_signoff(checks: list, commit_msg_file: str = "") -> int: commit_hash = get_commit_info("H") result = re.search(check['regex'], commit_msg) if result is None: - if not print_error_head.has_been_called: - print_error_head() + if not print_error_header.has_been_called: + print_error_header() print_error_message( check['check'], check['regex'], check['error'], commit_hash, diff --git a/commit_check/util.py b/commit_check/util.py index 1774ae6b..a63d6c11 100644 --- a/commit_check/util.py +++ b/commit_check/util.py @@ -109,7 +109,7 @@ def wrapper(*args, **kwargs): @track_print_call -def print_error_head(): +def print_error_header(): """Print error message. :returns: Print error head to user """ diff --git a/noxfile.py b/noxfile.py index e10a4f78..36001776 100644 --- a/noxfile.py +++ b/noxfile.py @@ -18,6 +18,8 @@ @nox.session() def lint(session): session.install("pre-commit") + # only need pre-commit hook for local development + session.run("pre-commit", "install", "--hook-type", "pre-commit") if session.posargs: args = session.posargs + ["--all-files"] else: diff --git a/tests/util_test.py b/tests/util_test.py index 70ae5b7e..0d53aa48 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -3,6 +3,7 @@ from commit_check.util import get_commit_info from commit_check.util import cmd_output from commit_check.util import validate_config +from commit_check.util import print_error_header from commit_check.util import print_error_message from commit_check.util import print_suggestion from subprocess import CalledProcessError, PIPE @@ -170,6 +171,13 @@ def test_validate_config_file_not_found(self, mocker): assert retval == {} class TestPrintErrorMessage: + def test_print_error_header(self, capfd): + # Must print on stdout with given argument. + print_error_header() + stdout, _ = capfd.readouterr() + assert "Commit rejected by Commit-Check" in stdout + assert "Commit rejected." in stdout + @pytest.mark.parametrize("check_type, type_failed_msg", [ ("message", "check failed =>"), ("branch", "check failed =>"), @@ -189,8 +197,6 @@ def test_print_error_message(self, capfd, check_type, type_failed_msg): dummy_reason ) stdout, _ = capfd.readouterr() - assert "Commit rejected by Commit-Check" in stdout - assert "Commit rejected." in stdout assert check_type in stdout assert type_failed_msg in stdout assert f"It doesn't match regex: {dummy_regex}" in stdout From 336407df0322d13405d29a5261331e63409cb200 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 11 Nov 2024 13:20:50 +0000 Subject: [PATCH 24/29] refactor: update commit-check.yml --- .commit-check.yml | 9 ++------- commit_check/__init__.py | 4 ++-- commit_check/branch.py | 4 ++-- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.commit-check.yml b/.commit-check.yml index 29c34f49..9b0c3199 100644 --- a/.commit-check.yml +++ b/.commit-check.yml @@ -29,11 +29,6 @@ checks: suggest: run command `git commit -m "conventional commit message" --signoff` - check: merge_base - regex: (main|master|develop|devel) + regex: main # it can be master, develop, devel etc based on your project. error: Current branch is not up to date with target branch - suggest: | - Please ensure your branch is up to date with the target branch by running: - git fetch origin main - git rebase origin/main - # If you encounter conflicts, resolve them and continue with: - git rebase --continue + suggest: please ensure your branch is rebased with the target branch diff --git a/commit_check/__init__.py b/commit_check/__init__.py index 34541d9a..44d3c0d4 100644 --- a/commit_check/__init__.py +++ b/commit_check/__init__.py @@ -50,9 +50,9 @@ }, { 'check': 'merge_base', - 'regex': r'(main|master|develop|devel)', + 'regex': r'main', # it can be master, develop, devel etc based on your project. 'error': 'Current branch is not up to date with target branch', - 'suggest': 'run command `git merge-base main HEAD`', + 'suggest': 'please ensure your branch is rebased with the target branch', }, ], } diff --git a/commit_check/branch.py b/commit_check/branch.py index 2823aca9..2283a541 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -49,7 +49,7 @@ def check_merge_base(checks: list) -> int: check['check'], check['regex'], check['error'], branch_name, ) - if check.get('suggest'): - print_suggestion(f"Run 'git rebase {check['regex']}' to update your branch") + if check('suggest'): + print_suggestion(check('suggest')) return FAIL return PASS From a73238afc6d76aeef6d931910af9d1d12b0c3a84 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 11 Nov 2024 14:01:04 +0000 Subject: [PATCH 25/29] test: add test for git_merge_base() --- tests/util_test.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/util_test.py b/tests/util_test.py index 0d53aa48..dc048314 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -1,5 +1,7 @@ import pytest +import subprocess from commit_check.util import get_branch_name +from commit_check.util import git_merge_base from commit_check.util import get_commit_info from commit_check.util import cmd_output from commit_check.util import validate_config @@ -7,6 +9,7 @@ from commit_check.util import print_error_message from commit_check.util import print_suggestion from subprocess import CalledProcessError, PIPE +from unittest.mock import MagicMock class TestUtil: @@ -43,6 +46,30 @@ def test_get_branch_name_with_exception(self, mocker): ] assert retval == "" + class TestGitMergeBase: + def test_git_merge_base_ancestor_exists(self, mocker): + mock_run = mocker.patch('subprocess.run') + mock_run.return_value = MagicMock(returncode=0) + result = git_merge_base('main', 'feature') + mock_run.assert_called_once_with(['git', 'merge-base', '--is-ancestor', 'main', 'feature'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8') + assert result == 0 + + def test_git_merge_base_no_ancestor(self, mocker): + mock_run = mocker.patch('subprocess.run') + mock_run.return_value = MagicMock(returncode=1) + + result = git_merge_base('main', 'feature') + + mock_run.assert_called_once_with(['git', 'merge-base', '--is-ancestor', 'main', 'feature'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8') + assert result == 1 + + def test_git_merge_base_with_exception(self, mocker): + mock_run = mocker.patch('subprocess.run') + mock_run.return_value = MagicMock(returncode=128) + mock_run.side_effect = CalledProcessError(128, 'git merge-base') + result = git_merge_base('main', 'feature') + assert result == 128 + class TestGetCommitInfo: @pytest.mark.parametrize("format_string", [ ("s"), From 8460825168b7828e51b4241e802a805fb906e692 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 11 Nov 2024 14:28:56 +0000 Subject: [PATCH 26/29] refactor: update util_test.py --- tests/util_test.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/util_test.py b/tests/util_test.py index dc048314..42870ea7 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -47,28 +47,28 @@ def test_get_branch_name_with_exception(self, mocker): assert retval == "" class TestGitMergeBase: - def test_git_merge_base_ancestor_exists(self, mocker): - mock_run = mocker.patch('subprocess.run') - mock_run.return_value = MagicMock(returncode=0) - result = git_merge_base('main', 'feature') - mock_run.assert_called_once_with(['git', 'merge-base', '--is-ancestor', 'main', 'feature'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8') - assert result == 0 - - def test_git_merge_base_no_ancestor(self, mocker): - mock_run = mocker.patch('subprocess.run') - mock_run.return_value = MagicMock(returncode=1) - - result = git_merge_base('main', 'feature') - - mock_run.assert_called_once_with(['git', 'merge-base', '--is-ancestor', 'main', 'feature'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8') - assert result == 1 - - def test_git_merge_base_with_exception(self, mocker): - mock_run = mocker.patch('subprocess.run') - mock_run.return_value = MagicMock(returncode=128) - mock_run.side_effect = CalledProcessError(128, 'git merge-base') - result = git_merge_base('main', 'feature') - assert result == 128 + @pytest.mark.parametrize("returncode,expected", [ + (0, 0), # ancestor exists + (1, 1), # no ancestor + (128, 128), # error case + ]) + def test_git_merge_base(self, mocker, returncode, expected): + mock_run = mocker.patch("subprocess.run") + if returncode == 128: + mock_run.side_effect = CalledProcessError(returncode, "git merge-base") + else: + mock_result = MagicMock() + mock_result.returncode = returncode + mock_run.return_value = mock_result + + result = git_merge_base("main", "feature") + + mock_run.assert_called_once_with( + ["git", "merge-base", "--is-ancestor", "main", "feature"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8' + ) + + assert result == expected class TestGetCommitInfo: @pytest.mark.parametrize("format_string", [ From 9a6d7946d882f1be438cb23bfdb2c05d72593d60 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 11 Nov 2024 21:35:42 +0200 Subject: [PATCH 27/29] feat: add new tests --- commit_check/branch.py | 4 ++-- tests/branch_test.py | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/commit_check/branch.py b/commit_check/branch.py index 2283a541..c2f1faa8 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -49,7 +49,7 @@ def check_merge_base(checks: list) -> int: check['check'], check['regex'], check['error'], branch_name, ) - if check('suggest'): - print_suggestion(check('suggest')) + if check['suggest']: + print_suggestion(check['suggest']) return FAIL return PASS diff --git a/tests/branch_test.py b/tests/branch_test.py index 7093eca0..2d54e0da 100644 --- a/tests/branch_test.py +++ b/tests/branch_test.py @@ -1,13 +1,12 @@ from commit_check import PASS, FAIL -from commit_check.branch import check_branch +from commit_check.branch import check_branch, check_merge_base # used by get_branch_name mock FAKE_BRANCH_NAME = "fake_branch_name" -# The location of check_branch() LOCATION = "commit_check.branch" -class TestBranch: +class TestCheckBranch: def test_check_branch(self, mocker): # Must call get_branch_name, re.match at once. checks = [{ @@ -113,3 +112,35 @@ def test_check_branch_with_result_none(self, mocker): assert m_re_match.call_count == 1 assert m_print_error_message.call_count == 1 assert m_print_suggestion.call_count == 1 + + +class TestCheckMergeBase: + def test_check_merge_base_pass(self, mocker): + # Must call get_merge_base at once. + checks = [{ + "check": "merge_base", + "regex": "main", + "error": "error", + "suggest": "suggest", + }] + mocker.patch( + f"{LOCATION}.check_merge_base", + return_value=0 + ) + retval = check_merge_base(checks) + assert retval == PASS + + def test_check_merge_base_fail(self, mocker): + # Must call get_merge_base at once. + checks = [{ + "check": "merge_base", + "regex": "abcdefg", + "error": "error", + "suggest": "suggest", + }] + mocker.patch( + f"{LOCATION}.check_merge_base", + return_value=1 + ) + retval = check_merge_base(checks) + assert retval == FAIL From f04cd23c3965388a071ec73151975c128d59baf1 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 11 Nov 2024 23:13:14 +0200 Subject: [PATCH 28/29] refactor: update test --- commit_check/branch.py | 6 +++--- tests/branch_test.py | 42 +++++++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/commit_check/branch.py b/commit_check/branch.py index c2f1faa8..2e235986 100644 --- a/commit_check/branch.py +++ b/commit_check/branch.py @@ -40,14 +40,14 @@ def check_merge_base(checks: list) -> int: f"{YELLOW}Not found target branch for checking merge base. skip checking.{RESET_COLOR}", ) return PASS - result = git_merge_base(check['regex'], 'HEAD') + current_branch = get_branch_name() + result = git_merge_base(check['regex'], current_branch) if result != 0: - branch_name = get_branch_name() if not print_error_header.has_been_called: print_error_header() print_error_message( check['check'], check['regex'], - check['error'], branch_name, + check['error'], current_branch, ) if check['suggest']: print_suggestion(check['suggest']) diff --git a/tests/branch_test.py b/tests/branch_test.py index 2d54e0da..fdded3de 100644 --- a/tests/branch_test.py +++ b/tests/branch_test.py @@ -115,32 +115,36 @@ def test_check_branch_with_result_none(self, mocker): class TestCheckMergeBase: - def test_check_merge_base_pass(self, mocker): - # Must call get_merge_base at once. + def test_check_merge_base_with_empty_checks(self, mocker): + checks = [] + m_check_merge = mocker.patch(f"{LOCATION}.check_merge_base") + retval = check_merge_base(checks) + assert retval == PASS + assert m_check_merge.call_count == 0 + + def test_check_merge_base_with_different_check(self, mocker): checks = [{ - "check": "merge_base", - "regex": "main", - "error": "error", - "suggest": "suggest", + "check": "branch", + "regex": "main" }] - mocker.patch( - f"{LOCATION}.check_merge_base", - return_value=0 - ) + m_check_merge = mocker.patch(f"{LOCATION}.check_merge_base") retval = check_merge_base(checks) assert retval == PASS + assert m_check_merge.call_count == 0 - def test_check_merge_base_fail(self, mocker): - # Must call get_merge_base at once. + def test_check_merge_base_fail_with_messages(self, mocker, capfd): checks = [{ "check": "merge_base", - "regex": "abcdefg", - "error": "error", - "suggest": "suggest", + "regex": "develop", + "error": "Current branch is not", + "suggest": "Please rebase" }] - mocker.patch( - f"{LOCATION}.check_merge_base", - return_value=1 - ) + mocker.patch(f"{LOCATION}.check_merge_base", return_value=1) + m_print_error = mocker.patch(f"{LOCATION}.print_error_message") + m_print_suggest = mocker.patch(f"{LOCATION}.print_suggestion") + retval = check_merge_base(checks) assert retval == FAIL + assert "Current branch is not" in m_print_error.call_args[0][2] + assert "Please rebase" in m_print_suggest.call_args[0][0] + print(m_print_error) From 2081ec1d354295005a421e47e321f0008a807861 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 11 Nov 2024 23:30:01 +0200 Subject: [PATCH 29/29] test: add tests for main and branch --- tests/branch_test.py | 1 - tests/main_test.py | 34 ++++++++++++++++++++++------------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/tests/branch_test.py b/tests/branch_test.py index fdded3de..64ec3a4e 100644 --- a/tests/branch_test.py +++ b/tests/branch_test.py @@ -147,4 +147,3 @@ def test_check_merge_base_fail_with_messages(self, mocker, capfd): assert retval == FAIL assert "Current branch is not" in m_print_error.call_args[0][2] assert "Please rebase" in m_print_suggest.call_args[0][0] - print(m_print_error) diff --git a/tests/main_test.py b/tests/main_test.py index 3fdc1849..9d26ebe2 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -7,18 +7,20 @@ class TestMain: - @pytest.mark.parametrize("argv, check_commit_call_count, check_branch_call_count, check_author_call_count, check_commit_signoff_call_count", [ - ([CMD, "--message"], 1, 0, 0, 0), - ([CMD, "--branch"], 0, 1, 0, 0), - ([CMD, "--author-name"], 0, 0, 1, 0), - ([CMD, "--author-email"], 0, 0, 1, 0), - ([CMD, "--commit-signoff"], 0, 0, 0, 1), - ([CMD, "--message", "--author-email"], 1, 0, 1, 0), - ([CMD, "--branch", "--message"], 1, 1, 0, 0), - ([CMD, "--author-name", "--author-email"], 0, 0, 2, 0), - ([CMD, "--message", "--branch", "--author-email"], 1, 1, 1, 0), - ([CMD, "--branch", "--message", "--author-name", "--author-email"], 1, 1, 2, 0), - ([CMD, "--dry-run"], 0, 0, 0, 0), + @pytest.mark.parametrize("argv, check_commit_call_count, check_branch_call_count, check_author_call_count, check_commit_signoff_call_count, check_merge_base_call_count", [ + ([CMD, "--message"], 1, 0, 0, 0, 0), + ([CMD, "--branch"], 0, 1, 0, 0, 0), + ([CMD, "--author-name"], 0, 0, 1, 0, 0), + ([CMD, "--author-email"], 0, 0, 1, 0, 0), + ([CMD, "--commit-signoff"], 0, 0, 0, 1, 0), + ([CMD, "--merge-base"], 0, 0, 0, 0, 1), + ([CMD, "--message", "--author-email"], 1, 0, 1, 0, 0), + ([CMD, "--branch", "--message"], 1, 1, 0, 0, 0), + ([CMD, "--author-name", "--author-email"], 0, 0, 2, 0, 0), + ([CMD, "--message", "--branch", "--author-email"], 1, 1, 1, 0, 0), + ([CMD, "--branch", "--message", "--author-name", "--author-email"], 1, 1, 2, 0, 0), + ([CMD, "--message", "--branch", "--author-name", "--author-email", "--commit-signoff", "--merge-base"], 1, 1, 2, 1, 1), + ([CMD, "--dry-run"], 0, 0, 0, 0, 0), ]) def test_main( self, @@ -28,6 +30,7 @@ def test_main( check_branch_call_count, check_author_call_count, check_commit_signoff_call_count, + check_merge_base_call_count, ): mocker.patch( "commit_check.main.validate_config", @@ -41,12 +44,14 @@ def test_main( m_check_branch = mocker.patch("commit_check.branch.check_branch") m_check_author = mocker.patch("commit_check.author.check_author") m_check_commit_signoff = mocker.patch("commit_check.commit.check_commit_signoff") + m_check_merge_base = mocker.patch("commit_check.branch.check_merge_base") sys.argv = argv main() assert m_check_commit.call_count == check_commit_call_count assert m_check_branch.call_count == check_branch_call_count assert m_check_author.call_count == check_author_call_count assert m_check_commit_signoff.call_count == check_commit_signoff_call_count + assert m_check_merge_base.call_count == check_merge_base_call_count def test_main_help(self, mocker, capfd): mocker.patch( @@ -61,6 +66,7 @@ def test_main_help(self, mocker, capfd): m_check_branch = mocker.patch("commit_check.branch.check_branch") m_check_author = mocker.patch("commit_check.author.check_author") m_check_commit_signoff = mocker.patch("commit_check.commit.check_commit_signoff") + m_check_merge_base = mocker.patch("commit_check.branch.check_merge_base") sys.argv = ["commit-check", "--h"] with pytest.raises(SystemExit): main() @@ -68,6 +74,7 @@ def test_main_help(self, mocker, capfd): assert m_check_branch.call_count == 0 assert m_check_author.call_count == 0 assert m_check_commit_signoff.call_count == 0 + assert m_check_merge_base.call_count == 0 stdout, _ = capfd.readouterr() assert "usage: " in stdout @@ -84,6 +91,7 @@ def test_main_version(self, mocker): m_check_branch = mocker.patch("commit_check.branch.check_branch") m_check_author = mocker.patch("commit_check.author.check_author") m_check_commit_signoff = mocker.patch("commit_check.commit.check_commit_signoff") + m_check_merge_base = mocker.patch("commit_check.branch.check_merge_base") sys.argv = ["commit-check", "--v"] with pytest.raises(SystemExit): main() @@ -91,6 +99,7 @@ def test_main_version(self, mocker): assert m_check_branch.call_count == 0 assert m_check_author.call_count == 0 assert m_check_commit_signoff.call_count == 0 + assert m_check_merge_base.call_count == 0 def test_main_validate_config_ret_none(self, mocker): mocker.patch( @@ -101,6 +110,7 @@ def test_main_validate_config_ret_none(self, mocker): mocker.patch("commit_check.branch.check_branch") mocker.patch("commit_check.author.check_author") mocker.patch("commit_check.commit.check_commit_signoff") + mocker.patch("commit_check.branch.check_merge_base") sys.argv = ["commit-check", "--message"] main() assert m_check_commit.call_count == 1