Skip to content

Commit 62a74a3

Browse files
shenxianpengpre-commit-ci[bot]coderabbitai[bot]
authored
feat: add merge_base to check the branch is rebased (#192)
* feat: check merge base (WIP) * feat: check merge base (WIP) * ci: auto fixes from pre-commit.com hooks * fix pre-commit check issues * Update commit_check/branch.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update .commit-check.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * feat: add noxfile.py * ci: auto fixes from pre-commit.com hooks * fix pre-commit check * Update noxfile.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: update merge_base feature * feat: refactor print error message * fix: update noxfile.py to fix workflow * fix: update noxfile.py to fix workflow * fix: update noxfile.py to fix finding wheel * fix: update noxfile.py * test: disable run commit-check * fix: revert main.yml * ci: auto fixes from pre-commit.com hooks * fix: removed does work test case * fix: update merge_base regex * fix: refactor code based on review * fix: update noxfile.py to fix lint * refactor: update commit-check.yml * test: add test for git_merge_base() * refactor: update util_test.py * feat: add new tests * refactor: update test * test: add tests for main and branch --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 651c76b commit 62a74a3

File tree

13 files changed

+268
-28
lines changed

13 files changed

+268
-28
lines changed

.commit-check.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,8 @@ checks:
2727
regex: Signed-off-by:.*[A-Za-z0-9]\s+<.+@.+>
2828
error: Signed-off-by not found in latest commit
2929
suggest: run command `git commit -m "conventional commit message" --signoff`
30+
31+
- check: merge_base
32+
regex: main # it can be master, develop, devel etc based on your project.
33+
error: Current branch is not up to date with target branch
34+
suggest: please ensure your branch is rebased with the target branch

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ __pycache__
44
.mypy_cache
55
.vscode
66
venv
7+
.venv
78
UNKNOWN.egg-info
89
dist
910
build
1011
tests/__pycache__
1112
.coverage
1213
coverage.xml
14+
.nox
15+
_build/
1316

1417
# docs
1518
docs/_build

commit_check/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
4848
'error': 'Signed-off-by not found in latest commit',
4949
'suggest': 'run command `git commit -m "conventional commit message" --signoff`',
5050
},
51+
{
52+
'check': 'merge_base',
53+
'regex': r'main', # it can be master, develop, devel etc based on your project.
54+
'error': 'Current branch is not up to date with target branch',
55+
'suggest': 'please ensure your branch is rebased with the target branch',
56+
},
5157
],
5258
}
5359

commit_check/author.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Check git author name and email"""
22
import re
33
from commit_check import YELLOW, RESET_COLOR, PASS, FAIL
4-
from commit_check.util import get_commit_info, print_error_message, print_suggestion
4+
from commit_check.util import get_commit_info, print_error_header, print_error_message, print_suggestion
55

66

77
def check_author(checks: list, check_type: str) -> int:
@@ -19,6 +19,8 @@ def check_author(checks: list, check_type: str) -> int:
1919
config_value = str(get_commit_info(format_str))
2020
result = re.match(check['regex'], config_value)
2121
if result is None:
22+
if not print_error_header.has_been_called:
23+
print_error_header()
2224
print_error_message(
2325
check['check'], check['regex'],
2426
check['error'], config_value,

commit_check/branch.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Check git branch naming convention."""
22
import re
33
from commit_check import YELLOW, RESET_COLOR, PASS, FAIL
4-
from commit_check.util import get_branch_name, print_error_message, print_suggestion
4+
from commit_check.util import get_branch_name, git_merge_base, print_error_header, print_error_message, print_suggestion
55

66

77
def check_branch(checks: list) -> int:
@@ -15,6 +15,8 @@ def check_branch(checks: list) -> int:
1515
branch_name = get_branch_name()
1616
result = re.match(check['regex'], branch_name)
1717
if result is None:
18+
if not print_error_header.has_been_called:
19+
print_error_header()
1820
print_error_message(
1921
check['check'], check['regex'],
2022
check['error'], branch_name,
@@ -23,3 +25,31 @@ def check_branch(checks: list) -> int:
2325
print_suggestion(check['suggest'])
2426
return FAIL
2527
return PASS
28+
29+
30+
def check_merge_base(checks: list) -> int:
31+
"""Check if the current branch is based on the latest target branch.
32+
params checks: List of check configurations containing merge_base rules
33+
34+
:returns PASS(0) if merge base check succeeds, FAIL(1) otherwise
35+
"""
36+
for check in checks:
37+
if check['check'] == 'merge_base':
38+
if check['regex'] == "":
39+
print(
40+
f"{YELLOW}Not found target branch for checking merge base. skip checking.{RESET_COLOR}",
41+
)
42+
return PASS
43+
current_branch = get_branch_name()
44+
result = git_merge_base(check['regex'], current_branch)
45+
if result != 0:
46+
if not print_error_header.has_been_called:
47+
print_error_header()
48+
print_error_message(
49+
check['check'], check['regex'],
50+
check['error'], current_branch,
51+
)
52+
if check['suggest']:
53+
print_suggestion(check['suggest'])
54+
return FAIL
55+
return PASS

commit_check/commit.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import re
33
from pathlib import PurePath
44
from commit_check import YELLOW, RESET_COLOR, PASS, FAIL
5-
from commit_check.util import cmd_output, get_commit_info, print_error_message, print_suggestion
5+
from commit_check.util import cmd_output, get_commit_info, print_error_header, print_error_message, print_suggestion
66

77

88
def get_default_commit_msg_file() -> str:
@@ -37,6 +37,8 @@ def check_commit_msg(checks: list, commit_msg_file: str = "") -> int:
3737
if check['check'] == 'message':
3838
result = re.match(check['regex'], commit_msg)
3939
if result is None:
40+
if not print_error_header.has_been_called:
41+
print_error_header()
4042
print_error_message(
4143
check['check'], check['regex'],
4244
check['error'], commit_msg,
@@ -64,6 +66,8 @@ def check_commit_signoff(checks: list, commit_msg_file: str = "") -> int:
6466
commit_hash = get_commit_info("H")
6567
result = re.search(check['regex'], commit_msg)
6668
if result is None:
69+
if not print_error_header.has_been_called:
70+
print_error_header()
6771
print_error_message(
6872
check['check'], check['regex'],
6973
check['error'], commit_hash,

commit_check/main.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ def get_parser() -> argparse.ArgumentParser:
7676
required=False,
7777
)
7878

79+
parser.add_argument(
80+
'-mb',
81+
'--merge-base',
82+
help='check common ancestors',
83+
action="store_true",
84+
required=False,
85+
)
86+
7987
parser.add_argument(
8088
'-d',
8189
'--dry-run',
@@ -108,6 +116,8 @@ def main() -> int:
108116
retval = branch.check_branch(checks)
109117
if args.commit_signoff:
110118
retval = commit.check_commit_signoff(checks)
119+
if args.merge_base:
120+
retval = branch.check_merge_base(checks)
111121

112122
if args.dry_run:
113123
retval = PASS

commit_check/util.py

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,23 @@ def get_commit_info(format_string: str, sha: str = "HEAD") -> str:
5151
return output
5252

5353

54+
def git_merge_base(target_branch: str, current_branch: str) -> int:
55+
"""Check ancestors for a given commit.
56+
:param target_branch: target branch
57+
:param current_branch: default is HEAD
58+
59+
:returns: 0 if ancestor exists, 1 if not, 128 if git command fails.
60+
"""
61+
try:
62+
commands = ['git', 'merge-base', '--is-ancestor', f'{target_branch}', f'{current_branch}']
63+
result = subprocess.run(
64+
commands, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8'
65+
)
66+
return result.returncode
67+
except CalledProcessError:
68+
return 128
69+
70+
5471
def cmd_output(commands: list) -> str:
5572
"""Run command
5673
:param commands: list of commands
@@ -83,14 +100,18 @@ def validate_config(path_to_config: str) -> dict:
83100
return configuration
84101

85102

86-
def print_error_message(check_type: str, regex: str, error: str, reason: str):
87-
"""Print error message.
88-
:param check_type:
89-
:param regex:
90-
:param error:
91-
:param reason:
103+
def track_print_call(func):
104+
def wrapper(*args, **kwargs):
105+
wrapper.has_been_called = True
106+
return func(*args, **kwargs)
107+
wrapper.has_been_called = False # Initialize as False
108+
return wrapper
92109

93-
:returns: Give error messages to user
110+
111+
@track_print_call
112+
def print_error_header():
113+
"""Print error message.
114+
:returns: Print error head to user
94115
"""
95116
print("Commit rejected by Commit-Check. ")
96117
print(" ")
@@ -105,10 +126,20 @@ def print_error_message(check_type: str, regex: str, error: str, reason: str):
105126
print(" ")
106127
print("Commit rejected. ")
107128
print(" ")
129+
130+
131+
def print_error_message(check_type: str, regex: str, error: str, reason: str):
132+
"""Print error message.
133+
:param check_type:
134+
:param regex:
135+
:param error:
136+
:param reason:
137+
138+
:returns: Give error messages to user
139+
"""
108140
print(f"Type {YELLOW}{check_type}{RESET_COLOR} check failed => {RED}{reason}{RESET_COLOR} ", end='',)
109141
print("")
110142
print(f"It doesn't match regex: {regex}")
111-
print("")
112143
print(error)
113144

114145

noxfile.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import nox
2+
import glob
3+
4+
nox.options.reuse_existing_virtualenvs = True
5+
nox.options.reuse_venv = True
6+
nox.options.sessions = ["lint"]
7+
8+
REQUIREMENTS = {
9+
"dev": "requirements-dev.txt",
10+
"docs": "docs/requirements.txt",
11+
}
12+
13+
# -----------------------------------------------------------------------------
14+
# Development Commands
15+
# -----------------------------------------------------------------------------
16+
17+
18+
@nox.session()
19+
def lint(session):
20+
session.install("pre-commit")
21+
# only need pre-commit hook for local development
22+
session.run("pre-commit", "install", "--hook-type", "pre-commit")
23+
if session.posargs:
24+
args = session.posargs + ["--all-files"]
25+
else:
26+
args = ["--all-files", "--show-diff-on-failure"]
27+
28+
session.run("pre-commit", "run", *args)
29+
30+
31+
@nox.session(name="test-hook")
32+
def test_hook(session):
33+
session.install("-e", ".")
34+
session.install("pre-commit")
35+
session.run("pre-commit", "try-repo", ".")
36+
37+
38+
@nox.session()
39+
def build(session):
40+
session.run("python3", "-m", "pip", "wheel", "--no-deps", "-w", "dist", ".")
41+
42+
43+
@nox.session(name="install-wheel", requires=["build"])
44+
def install_wheel(session):
45+
whl_file = glob.glob("dist/*.whl")
46+
session.install(str(whl_file[0]))
47+
48+
49+
# @nox.session(name="commit-check", requires=["install-wheel"])
50+
@nox.session(name="commit-check", requires=["install-wheel"])
51+
def commit_check(session):
52+
session.run(
53+
"commit-check",
54+
"--message",
55+
"--branch",
56+
"--author-email",
57+
)
58+
59+
60+
@nox.session(requires=["install-wheel"])
61+
def coverage(session):
62+
session.run("coverage", "run", "--source", "commit_check", "-m", "pytest")
63+
session.run("coverage", "report")
64+
session.run("coverage", "xml")
65+
66+
67+
@nox.session()
68+
def docs(session):
69+
session.install("-e", ".")
70+
session.install("-r", REQUIREMENTS["docs"])
71+
session.run("sphinx-build", "-E", "-W", "-b", "html", "docs", "_build/html")

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
coverage
2+
nox
23
pre-commit
34
pytest
45
pytest-mock

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy