| | import os |
| | import subprocess |
| | from collections import Counter |
| |
|
| | CONFIG_FILE_EXTENSIONS = (".json", ".yml", ".yaml", ".ini", ".conf", ".toml") |
| |
|
| |
|
| | def is_text_file(filepath): |
| | |
| | try: |
| | with open(filepath, "rb") as f: |
| | chunk = f.read(4096) |
| | if b"\0" in chunk: |
| | return False |
| | return True |
| | except Exception: |
| | return False |
| |
|
| |
|
| | def should_skip_file(path): |
| | base = os.path.basename(path) |
| | |
| | if base.startswith("."): |
| | return True |
| | |
| | if base.lower().endswith(CONFIG_FILE_EXTENSIONS): |
| | return True |
| | return False |
| |
|
| |
|
| | def get_tracked_files(): |
| | try: |
| | output = subprocess.check_output(["git", "ls-files"], text=True) |
| | files = output.strip().split("\n") |
| | files = [f for f in files if f and os.path.isfile(f)] |
| | return files |
| | except subprocess.CalledProcessError: |
| | print("Error: Are you in a git repository?") |
| | return [] |
| |
|
| |
|
| | def main(): |
| | files = get_tracked_files() |
| | email_counter = Counter() |
| | total_lines = 0 |
| |
|
| | for file in files: |
| | if should_skip_file(file): |
| | continue |
| | if not is_text_file(file): |
| | continue |
| | try: |
| | blame = subprocess.check_output( |
| | ["git", "blame", "-e", file], text=True, errors="replace" |
| | ) |
| | for line in blame.splitlines(): |
| | |
| | if "<" in line and ">" in line: |
| | try: |
| | email = line.split("<")[1].split(">")[0].strip() |
| | except Exception: |
| | continue |
| | email_counter[email] += 1 |
| | total_lines += 1 |
| | except subprocess.CalledProcessError: |
| | continue |
| |
|
| | for email, lines in email_counter.most_common(): |
| | percent = (lines / total_lines * 100) if total_lines else 0 |
| | print(f"{email}: {lines}/{total_lines} {percent:.2f}%") |
| |
|
| |
|
| | if __name__ == "__main__": |
| | main() |
| |
|