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