Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Prev Previous commit
Next Next commit
Implement GitHub user matching feature for issue #93
Co-authored-by: shenxianpeng <3353385+shenxianpeng@users.noreply.github.com>
  • Loading branch information
Copilot and shenxianpeng committed Aug 19, 2025
commit 0fb84c0b47c16504615e4866356b931c1ca6f202
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
venv/
.venv/
test_env/
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ jobs:
imperative: false
job-summary: true
pr-comments: ${{ github.event_name == 'pull_request' }}
author-name-match-github: false
author-email-match-github: false
```

## Used By
Expand Down Expand Up @@ -138,6 +140,21 @@ jobs:
> `pr-comments` is an experimental feature. By default, it's disabled. To use it, you need to set `GITHUB_TOKEN` in the GitHub Action.
>
> This feature currently doesn’t work with forked repositories. For more details, refer to issue [#77](https://github.com/commit-check/commit-check-action/issues/77).
### `author-name-match-github`

- **Description**: check if committer name matches GitHub user profile.
- Default: `false`

> [!IMPORTANT]
> `author-name-match-github` requires `GITHUB_TOKEN` to be set in the GitHub Action to access the GitHub API.

### `author-email-match-github`

- **Description**: check if committer email matches GitHub user profile.
- Default: `false`

> [!IMPORTANT]
> `author-email-match-github` requires `GITHUB_TOKEN` to be set in the GitHub Action to access the GitHub API.

Note: the default rule of above inputs is following [this configuration](https://github.com/commit-check/commit-check/blob/main/.commit-check.yml). If you want to customize, just add your `.commit-check.yml` config file under your repository root directory.

Expand Down
Binary file added __pycache__/main.cpython-312.pyc
Binary file not shown.
10 changes: 10 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ inputs:
description: post results to the pull request comments
required: false
default: false
author-name-match-github:
description: check if committer name matches GitHub user profile
required: false
default: false
author-email-match-github:
description: check if committer email matches GitHub user profile
required: false
default: false
runs:
using: "composite"
steps:
Expand Down Expand Up @@ -84,3 +92,5 @@ runs:
DRY_RUN: ${{ inputs.dry-run }}
JOB_SUMMARY: ${{ inputs.job-summary }}
PR_COMMENTS: ${{ inputs.pr-comments }}
AUTHOR_NAME_MATCH_GITHUB: ${{ inputs.author-name-match-github }}
AUTHOR_EMAIL_MATCH_GITHUB: ${{ inputs.author-email-match-github }}
114 changes: 109 additions & 5 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
DRY_RUN = os.getenv("DRY_RUN", "false")
JOB_SUMMARY = os.getenv("JOB_SUMMARY", "false")
PR_COMMENTS = os.getenv("PR_COMMENTS", "false")
AUTHOR_NAME_MATCH_GITHUB = os.getenv("AUTHOR_NAME_MATCH_GITHUB", "false")
AUTHOR_EMAIL_MATCH_GITHUB = os.getenv("AUTHOR_EMAIL_MATCH_GITHUB", "false")
GITHUB_STEP_SUMMARY = os.environ["GITHUB_STEP_SUMMARY"]
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY")
Expand All @@ -38,7 +40,9 @@ def log_env_vars():
print(f"IMPERATIVE = {IMPERATIVE}")
print(f"DRY_RUN = {DRY_RUN}")
print(f"JOB_SUMMARY = {JOB_SUMMARY}")
print(f"PR_COMMENTS = {PR_COMMENTS}\n")
print(f"PR_COMMENTS = {PR_COMMENTS}")
print(f"AUTHOR_NAME_MATCH_GITHUB = {AUTHOR_NAME_MATCH_GITHUB}")
print(f"AUTHOR_EMAIL_MATCH_GITHUB = {AUTHOR_EMAIL_MATCH_GITHUB}\n")


def run_commit_check() -> int:
Expand Down Expand Up @@ -80,13 +84,26 @@ def run_commit_check() -> int:

def read_result_file() -> str | None:
"""Reads the result.txt file and removes ANSI color codes."""
if os.path.getsize("result.txt") > 0:
result_content = ""

# Read commit-check results
if os.path.exists("result.txt") and os.path.getsize("result.txt") > 0:
with open("result.txt", "r") as result_file:
result_text = re.sub(
commit_check_result = re.sub(
r"\x1B\[[0-9;]*[a-zA-Z]", "", result_file.read()
) # Remove ANSI colors
return result_text.rstrip()
return None
result_content += commit_check_result.rstrip()

# Read GitHub user check results
if os.path.exists("github_user_check_result.txt") and os.path.getsize("github_user_check_result.txt") > 0:
with open("github_user_check_result.txt", "r") as github_result_file:
github_check_result = github_result_file.read().rstrip()
if result_content:
result_content += "\n\n" + github_check_result
else:
result_content = github_check_result

return result_content if result_content else None


def add_job_summary() -> int:
Expand Down Expand Up @@ -174,6 +191,92 @@ def add_pr_comments() -> int:
return 1


def get_git_committer_info() -> tuple[str, str]:
"""Gets the committer name and email from the latest commit."""
try:
# Get committer name
name_result = subprocess.run(
["git", "log", "-1", "--pretty=format:%cn"],
capture_output=True,
text=True,
check=True
)
committer_name = name_result.stdout.strip()

# Get committer email
email_result = subprocess.run(
["git", "log", "-1", "--pretty=format:%ce"],
capture_output=True,
text=True,
check=True
)
committer_email = email_result.stdout.strip()

return committer_name, committer_email
except subprocess.CalledProcessError as e:
print(f"Error getting git committer info: {e}", file=sys.stderr)
return "", ""


def check_github_user_match() -> int:
"""Checks if committer name and email match GitHub user profile."""
if AUTHOR_NAME_MATCH_GITHUB == "false" and AUTHOR_EMAIL_MATCH_GITHUB == "false":
return 0

try:
token = os.getenv("GITHUB_TOKEN")
if not token:
print("Error: GITHUB_TOKEN is required for GitHub user matching checks", file=sys.stderr)
return 1

# Get committer info from git
committer_name, committer_email = get_git_committer_info()
if not committer_name and not committer_email:
print("Error: Could not retrieve git committer information", file=sys.stderr)
return 1

# Initialize GitHub client
g = Github(token)
user = g.get_user()

errors = []

# Check name if enabled
if AUTHOR_NAME_MATCH_GITHUB == "true":
github_name = user.name or ""
if committer_name != github_name:
errors.append(f"Committer name '{committer_name}' does not match GitHub user name '{github_name}'")

# Check email if enabled
if AUTHOR_EMAIL_MATCH_GITHUB == "true":
github_email = user.email or ""
# Also check against public emails if primary email is private
if committer_email != github_email:
# Check public emails
try:
public_emails = [email.email for email in user.get_emails() if email.verified]
if committer_email not in public_emails:
errors.append(f"Committer email '{committer_email}' does not match any verified GitHub user email")
except Exception:
# If we can't access emails (permissions issue), just check the primary
errors.append(f"Committer email '{committer_email}' does not match GitHub user email '{github_email}'")

if errors:
# Write errors to result file
with open("github_user_check_result.txt", "w") as result_file:
for error in errors:
result_file.write(f"❌ {error}\n")
return 1

return 0

except Exception as e:
print(f"Error checking GitHub user match: {e}", file=sys.stderr)
with open("github_user_check_result.txt", "w") as result_file:
result_file.write(f"❌ Error checking GitHub user match: {e}\n")
return 1


def log_error_and_exit(
failure_title: str, result_text: str | None, ret_code: int
) -> None:
Expand All @@ -197,6 +300,7 @@ def main():

# Combine return codes
ret_code = run_commit_check()
ret_code += check_github_user_match()
ret_code += add_job_summary()
ret_code += add_pr_comments()

Expand Down
Loading