mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
The AUTHOR_MAP dict in scripts/release.py was a merge-conflict magnet: every concurrent salvage PR appended entries to the same lines of the same file, so parallel PRs re-conflicted on every merge to main. New system: one file per email under contributors/emails/ — filename is the commit-author email, first non-comment line is the GitHub login. File additions never conflict, so any number of PRs can add mappings concurrently. - scripts/release.py: AUTHOR_MAP is now LEGACY_AUTHOR_MAP (frozen) merged with the directory at import time (directory wins). All existing consumers (resolve_author, contributor_audit.py) unchanged. - scripts/add_contributor.py: idempotent CLI to add a mapping; refuses conflicting reassignments (incl. against the legacy map), validates email/login shapes. - contributor-check.yml: attribution gate now accepts a mapping file OR a legacy entry; failure message prints the exact add_contributor command. Also auto-resolves bare <login>@users.noreply.github.com emails is intentionally NOT added (kept id+login form only, matching previous behavior). - contributor_audit.py: guidance now points at add_contributor.py. - tests/scripts/test_contributor_map.py: 12 tests covering loader, merge precedence, CLI idempotency/conflict/validation, subprocess E2E.
99 lines
3.1 KiB
Python
99 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Add a contributor email → GitHub login mapping.
|
|
|
|
Writes one file per email under contributors/emails/ (filename = email,
|
|
content = login). File additions never merge-conflict, unlike the legacy
|
|
AUTHOR_MAP dict in scripts/release.py, which is frozen — do not append to it.
|
|
|
|
Usage (from the repo root):
|
|
python3 scripts/add_contributor.py <email> <github-login> [comment...]
|
|
|
|
# e.g.
|
|
python3 scripts/add_contributor.py jane@example.com janedoe "PR #12345 salvage"
|
|
|
|
Idempotent: if the mapping already exists with the same login, prints
|
|
"present" and exits 0. If the email maps to a DIFFERENT login (here or in the
|
|
legacy AUTHOR_MAP), refuses with exit 1 so a typo can't silently reassign
|
|
someone's commits.
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
EMAILS_DIR = REPO_ROOT / "contributors" / "emails"
|
|
|
|
_EMAIL_RE = re.compile(r"^[^/\\\s]+@[^/\\\s]+$")
|
|
_LOGIN_RE = re.compile(r"^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$")
|
|
|
|
|
|
def read_mapping_file(path: Path) -> str | None:
|
|
"""Return the login from a mapping file (first non-comment line)."""
|
|
try:
|
|
for line in path.read_text(encoding="utf-8").splitlines():
|
|
line = line.strip()
|
|
if line and not line.startswith("#"):
|
|
return line
|
|
except OSError:
|
|
pass
|
|
return None
|
|
|
|
|
|
def _legacy_login(email: str) -> str | None:
|
|
"""Look the email up in the frozen legacy AUTHOR_MAP in release.py."""
|
|
try:
|
|
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
from release import LEGACY_AUTHOR_MAP # noqa: PLC0415
|
|
|
|
return LEGACY_AUTHOR_MAP.get(email)
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def add_contributor(email: str, login: str, comment: str = "") -> int:
|
|
email = email.strip()
|
|
login = login.strip().lstrip("@")
|
|
|
|
if not _EMAIL_RE.match(email):
|
|
print(f"error: {email!r} does not look like a commit-author email", file=sys.stderr)
|
|
return 2
|
|
if not _LOGIN_RE.match(login):
|
|
print(f"error: {login!r} is not a valid GitHub login", file=sys.stderr)
|
|
return 2
|
|
|
|
path = EMAILS_DIR / email
|
|
existing = read_mapping_file(path) if path.is_file() else None
|
|
if existing is None:
|
|
existing = _legacy_login(email)
|
|
if existing is not None:
|
|
if existing == login:
|
|
print("present")
|
|
return 0
|
|
print(
|
|
f"error: {email} already maps to {existing!r} (asked for {login!r}) — "
|
|
"resolve manually",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
EMAILS_DIR.mkdir(parents=True, exist_ok=True)
|
|
body = login + "\n"
|
|
if comment:
|
|
body += f"# {comment}\n"
|
|
path.write_text(body, encoding="utf-8")
|
|
print(f"added: contributors/emails/{email} -> {login}")
|
|
return 0
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) < 3:
|
|
print(__doc__, file=sys.stderr)
|
|
return 2
|
|
email, login = sys.argv[1], sys.argv[2]
|
|
comment = " ".join(sys.argv[3:])
|
|
return add_contributor(email, login, comment)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|