contribution_stats.py 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import os
  2. import subprocess
  3. from collections import Counter
  4. CONFIG_FILE_EXTENSIONS = (".json", ".yml", ".yaml", ".ini", ".conf", ".toml")
  5. def is_text_file(filepath):
  6. # Check for binary file by scanning for null bytes.
  7. try:
  8. with open(filepath, "rb") as f:
  9. chunk = f.read(4096)
  10. if b"\0" in chunk:
  11. return False
  12. return True
  13. except Exception:
  14. return False
  15. def should_skip_file(path):
  16. base = os.path.basename(path)
  17. # Skip dotfiles and dotdirs
  18. if base.startswith("."):
  19. return True
  20. # Skip config files by extension
  21. if base.lower().endswith(CONFIG_FILE_EXTENSIONS):
  22. return True
  23. return False
  24. def get_tracked_files():
  25. try:
  26. output = subprocess.check_output(["git", "ls-files"], text=True)
  27. files = output.strip().split("\n")
  28. files = [f for f in files if f and os.path.isfile(f)]
  29. return files
  30. except subprocess.CalledProcessError:
  31. print("Error: Are you in a git repository?")
  32. return []
  33. def main():
  34. files = get_tracked_files()
  35. email_counter = Counter()
  36. total_lines = 0
  37. for file in files:
  38. if should_skip_file(file):
  39. continue
  40. if not is_text_file(file):
  41. continue
  42. try:
  43. blame = subprocess.check_output(
  44. ["git", "blame", "-e", file], text=True, errors="replace"
  45. )
  46. for line in blame.splitlines():
  47. # The email always inside <>
  48. if "<" in line and ">" in line:
  49. try:
  50. email = line.split("<")[1].split(">")[0].strip()
  51. except Exception:
  52. continue
  53. email_counter[email] += 1
  54. total_lines += 1
  55. except subprocess.CalledProcessError:
  56. continue
  57. for email, lines in email_counter.most_common():
  58. percent = (lines / total_lines * 100) if total_lines else 0
  59. print(f"{email}: {lines}/{total_lines} {percent:.2f}%")
  60. if __name__ == "__main__":
  61. main()