Skip to content

Commit 1e23144

Browse files
committed
allow passing ignore_emails and ignore_logins
1 parent e5a6e64 commit 1e23144

File tree

3 files changed

+129
-17
lines changed

3 files changed

+129
-17
lines changed

README.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ Why this is useful
77
- Helps projects maintain reproducible credit and citation information
88

99
What this repository provides
10-
- A Python script at `.github/scripts/check_contributors.py` that performs the check
11-
- A GitHub Actions workflow at `.github/workflows/metadata-check.yml` that runs the script on PR events
12-
- A sample configuration `.github/contrib-metadata-check.yml` to control behavior
10+
- A Python script at `check_contributors.py` that performs the check
11+
- A GitHub Actions bot at `action.yml` that runs the script on PR events
1312

1413
How it works
1514
- The action runs on PR events. It runs `git log --use-mailmap --format='%aN <%aE>' BASE..HEAD` to collect commit authors, so ensure `.mailmap` is present if you need to unify multiple emails.
@@ -21,17 +20,24 @@ How it works
2120

2221
1. Ensure your repository has `CITATION.cff` and/or `codemeta.json` with author/contributor entries.
2322
2. Add a `.mailmap` at the repository root if you need to unify alternate emails or names from the git history.
24-
3. Add this action (or copy the workflow) into your repo; the included workflow triggers on pull requests.
23+
3. Add this action (or copy the workflow) into your repo in `.github/workflows/contrib-check.yml`; the included workflow triggers on pull requests.
2524

2625

27-
### Example `contrib-metadata-check.yml`
26+
### Example `.github/workflows/contrib-check.yml`
2827

2928
```yaml
30-
mode: warn # "warn" (default) or "fail"
31-
ignore_emails:
32-
- dependabot[bot]@users.noreply.github.com
33-
ignore_logins:
34-
- dependabot[bot]
29+
jobs:
30+
contrib-check:
31+
runs-on: ubuntu-latest
32+
33+
steps:
34+
- name: Contrib metadata check
35+
uses: vuillaut/contrib-checker@main
36+
with:
37+
github_token: ${{ secrets.GITHUB_TOKEN }}
38+
mode: warn # or 'fail' to make the workflow fail when contributors are missing
39+
ignore_emails: "dependabot[bot]@users.noreply.github.com,[email protected],[email protected]"
40+
ignore_logins: "dependabot[bot],github-actions[bot],ci-bot"
3541
```
3642
3743
## Requirements

action.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ inputs:
77
description: "Behavior mode: 'warn' (default) or 'fail'. 'warn' posts a comment but doesn't fail the CI, while 'fail' posts a comment and fails the CI if contributors are missing."
88
required: false
99
default: warn
10+
ignore_emails:
11+
description: "Comma-separated list of email addresses to ignore when checking contributors (e.g., '[email protected],[email protected]')"
12+
required: false
13+
default: ""
14+
ignore_logins:
15+
description: "Comma-separated list of login names to ignore when checking contributors (e.g., 'dependabot[bot],ci-bot')"
16+
required: false
17+
default: ""
1018

1119

1220
runs:
@@ -28,4 +36,8 @@ runs:
2836

2937
- name: Run check script
3038
run: python check_contributors.py
31-
shell: bash
39+
shell: bash
40+
env:
41+
ACTION_MODE: ${{ inputs.mode }}
42+
ACTION_IGNORE_EMAILS: ${{ inputs.ignore_emails }}
43+
ACTION_IGNORE_LOGINS: ${{ inputs.ignore_logins }}

check_contributors.py

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ def _load_config(self):
4848
default.update(data)
4949
except Exception as e:
5050
print(f"Warning loading config: {e}")
51+
52+
# Override with action inputs if provided
53+
action_mode = os.environ.get('ACTION_MODE')
54+
if action_mode:
55+
default['mode'] = action_mode
56+
57+
action_ignore_emails = os.environ.get('ACTION_IGNORE_EMAILS', '').strip()
58+
if action_ignore_emails:
59+
emails = [email.strip() for email in action_ignore_emails.split(',') if email.strip()]
60+
default['ignore_emails'] = emails
61+
62+
action_ignore_logins = os.environ.get('ACTION_IGNORE_LOGINS', '').strip()
63+
if action_ignore_logins:
64+
logins = [login.strip() for login in action_ignore_logins.split(',') if login.strip()]
65+
default['ignore_logins'] = logins
66+
5167
return default
5268

5369
def _run_git(self, args: list) -> str:
@@ -62,6 +78,13 @@ def should_include_contributor(self, contributor: str) -> bool:
6278
m = re.search(r'<([^>]+)>', contributor)
6379
if m and m.group(1) in self.config.get('ignore_emails', []):
6480
return False
81+
82+
# Check ignore_logins - extract potential login from contributor string
83+
ignore_logins = self.config.get('ignore_logins', [])
84+
for login in ignore_logins:
85+
if login.lower() in contributor.lower():
86+
return False
87+
6588
lower = contributor.lower()
6689
if 'bot' in lower or 'dependabot' in lower:
6790
return False
@@ -177,23 +200,94 @@ def check_contributors(self) -> bool:
177200
print(f'Found {len(pr_contribs)} contributors in PR commits')
178201
for c in sorted(pr_contribs):
179202
print(f' - {c}')
180-
metadata = self.parse_citation_cff() | self.parse_codemeta_json()
203+
204+
# Check each metadata file separately
205+
citation_cff = self.parse_citation_cff()
206+
codemeta_json = self.parse_codemeta_json()
207+
208+
print(f'\nChecking CITATION.cff:')
209+
if citation_cff:
210+
print(f' Found {len(citation_cff)} contributors in CITATION.cff')
211+
for c in sorted(citation_cff):
212+
print(f' - {c}')
213+
missing_citation = self.find_missing_contributors(pr_contribs, citation_cff)
214+
if missing_citation:
215+
print(f' Missing from CITATION.cff: {sorted(missing_citation)}')
216+
else:
217+
print(' All PR contributors present in CITATION.cff')
218+
else:
219+
print(' CITATION.cff not found or empty')
220+
221+
print(f'\nChecking codemeta.json:')
222+
if codemeta_json:
223+
print(f' Found {len(codemeta_json)} contributors in codemeta.json')
224+
for c in sorted(codemeta_json):
225+
print(f' - {c}')
226+
missing_codemeta = self.find_missing_contributors(pr_contribs, codemeta_json)
227+
if missing_codemeta:
228+
print(f' Missing from codemeta.json: {sorted(missing_codemeta)}')
229+
else:
230+
print(' All PR contributors present in codemeta.json')
231+
else:
232+
print(' codemeta.json not found or empty')
233+
234+
# Overall check (union of both files)
235+
metadata = citation_cff | codemeta_json
181236
missing = self.find_missing_contributors(pr_contribs, metadata)
237+
238+
print(f'\nOverall result:')
182239
if missing:
183-
print(f'Missing contributors: {sorted(missing)}')
240+
print(f'Missing contributors (not in any metadata file): {sorted(missing)}')
184241
self.post_pr_comment(missing)
185242
return False if self.config.get('mode') == 'fail' else True
186-
print('All PR contributors present in metadata')
243+
print('All PR contributors present in at least one metadata file')
187244
return True
188245

189246
def check_all_contributors_in_metadata(self) -> bool:
190247
allc = self.get_all_contributors()
191-
metadata = self.parse_citation_cff() | self.parse_codemeta_json()
248+
print(f'Found {len(allc)} total contributors in repository')
249+
for c in sorted(allc):
250+
print(f' - {c}')
251+
252+
# Check each metadata file separately
253+
citation_cff = self.parse_citation_cff()
254+
codemeta_json = self.parse_codemeta_json()
255+
256+
print('\nChecking CITATION.cff:')
257+
if citation_cff:
258+
print(f' Found {len(citation_cff)} contributors in CITATION.cff')
259+
for c in sorted(citation_cff):
260+
print(f' - {c}')
261+
missing_citation = self.find_missing_contributors(allc, citation_cff)
262+
if missing_citation:
263+
print(f' Missing from CITATION.cff: {sorted(missing_citation)}')
264+
else:
265+
print(' All repository contributors present in CITATION.cff')
266+
else:
267+
print(' CITATION.cff not found or empty')
268+
269+
print('\nChecking codemeta.json:')
270+
if codemeta_json:
271+
print(f' Found {len(codemeta_json)} contributors in codemeta.json')
272+
for c in sorted(codemeta_json):
273+
print(f' - {c}')
274+
missing_codemeta = self.find_missing_contributors(allc, codemeta_json)
275+
if missing_codemeta:
276+
print(f' Missing from codemeta.json: {sorted(missing_codemeta)}')
277+
else:
278+
print(' All repository contributors present in codemeta.json')
279+
else:
280+
print(' codemeta.json not found or empty')
281+
282+
# Overall check (union of both files)
283+
metadata = citation_cff | codemeta_json
192284
missing = self.find_missing_contributors(allc, metadata)
285+
286+
print('\nOverall result:')
193287
if missing:
194-
print(f'Missing contributors in repo: {sorted(missing)}')
288+
print(f'Missing contributors (not in any metadata file): {sorted(missing)}')
195289
return False
196-
print('All contributors present in metadata files')
290+
print('All contributors present in at least one metadata file')
197291
return True
198292

199293

0 commit comments

Comments
 (0)