|
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() |
|
|