Add SPDX attribution headers to source files based on git blame.
| 26 | |
| 27 | |
| 28 | class SPDXAttributor: |
| 29 | """Add SPDX attribution headers to source files based on git blame.""" |
| 30 | |
| 31 | def __init__(self, repo_root: str, dry_run: bool = False): |
| 32 | """Initialize the attributor with a repository root and options.""" |
| 33 | self.repo_root = Path(repo_root).resolve() |
| 34 | self.dry_run = dry_run |
| 35 | self.exclude_patterns = self._load_exclude_patterns() |
| 36 | self.mailmap = self._parse_mailmap() |
| 37 | self.company_domains = self._build_company_domain_map() |
| 38 | |
| 39 | def _load_exclude_patterns(self) -> List[str]: |
| 40 | """Load exclusion patterns from .spdx-exclude file.""" |
| 41 | exclude_file = self.repo_root / ".spdx-exclude" |
| 42 | patterns = [] |
| 43 | |
| 44 | if exclude_file.exists(): |
| 45 | with open(exclude_file, "r") as f: |
| 46 | for line in f: |
| 47 | line = line.strip() |
| 48 | if line and not line.startswith("#"): |
| 49 | patterns.append(line) |
| 50 | |
| 51 | return patterns |
| 52 | |
| 53 | def _parse_mailmap(self) -> Dict[str, Tuple[str, str]]: |
| 54 | """Parse .mailmap file to get canonical name/email mappings.""" |
| 55 | mailmap_file = self.repo_root / ".mailmap" |
| 56 | mailmap = {} |
| 57 | |
| 58 | if not mailmap_file.exists(): |
| 59 | print("Warning: .mailmap file not found") |
| 60 | return mailmap |
| 61 | |
| 62 | with open(mailmap_file, "r") as f: |
| 63 | for line in f: |
| 64 | line = line.strip() |
| 65 | if not line or line.startswith("#"): |
| 66 | continue |
| 67 | |
| 68 | # Parse mailmap format: "Proper Name <proper@email.com> <commit@email.com>" |
| 69 | # or "Proper Name <proper@email.com> Commit Name <commit@email.com>" |
| 70 | if ">" in line: |
| 71 | parts = line.split(">") |
| 72 | if len(parts) >= 2: |
| 73 | proper_part = parts[0].strip() |
| 74 | commit_part = parts[1].strip() |
| 75 | |
| 76 | # Extract proper name and email |
| 77 | if "<" in proper_part: |
| 78 | proper_name = proper_part.split("<")[0].strip() |
| 79 | proper_email = proper_part.split("<")[1].strip() |
| 80 | else: |
| 81 | continue |
| 82 | |
| 83 | # Extract commit email (and possibly name) |
| 84 | if "<" in commit_part: |
| 85 | commit_email = ( |