Spaces:
Running
Running
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() | |