1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 |
- import os
- import subprocess
- from collections import Counter
- CONFIG_FILE_EXTENSIONS = (".json", ".yml", ".yaml", ".ini", ".conf", ".toml")
- def is_text_file(filepath):
- # Check for binary file by scanning for null bytes.
- 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)
- # Skip dotfiles and dotdirs
- if base.startswith("."):
- return True
- # Skip config files by extension
- 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():
- # The email always inside <>
- 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()
|