|
3 | 3 | """
|
4 | 4 |
|
5 | 5 | import collections
|
| 6 | +import re |
6 | 7 | from datetime import datetime, timezone
|
7 | 8 | from typing import Dict, Optional
|
8 | 9 |
|
@@ -285,13 +286,103 @@ def git_commits_per_author() -> None:
|
285 | 286 | Shows the number of commits per author.
|
286 | 287 | """
|
287 | 288 |
|
288 |
| - cmd = ['git', 'shortlog', '-s', '-n'] |
| 289 | + # Original authors command: |
| 290 | + # git -c log.showSignature=false log --use-mailmap \ |
| 291 | + # $_merges "$_since" "$_until" $_log_options \ |
| 292 | + # | grep -i Author: | cut -c9- |
| 293 | + |
| 294 | + # Original co-authors command: |
| 295 | + # git -c log.showSignature=false log --author="$c" \ |
| 296 | + # --reverse --use-mailmap $_merges "$_since" "$_until" \ |
| 297 | + # --format='%at' $_log_options $_pathspec | head -n 1 |
| 298 | + cmd = [ |
| 299 | + 'git', |
| 300 | + '-c', 'log.showSignature=false', |
| 301 | + 'log', |
| 302 | + '--use-mailmap', |
| 303 | + '--no-merges', |
| 304 | + '--pretty=format:Author:%aN <%aE>%n%b' |
| 305 | + ] |
| 306 | + |
289 | 307 | output = run_git_command(cmd)
|
290 |
| - if output: |
291 |
| - print("Git commits per author:") |
292 |
| - print(output) |
293 |
| - else: |
| 308 | + if not output: |
294 | 309 | print('No commits found.')
|
| 310 | + return |
| 311 | + |
| 312 | + # Initialize commit count dictionary |
| 313 | + commit_counts = {} |
| 314 | + |
| 315 | + # Total commits (including co-authored commits) |
| 316 | + total_commits = 0 |
| 317 | + |
| 318 | + # Regular expressions for parsing the author(s) |
| 319 | + author_regex = re.compile(r'^Author:\s*(.+)$', re.IGNORECASE) |
| 320 | + coauthor_regex = re.compile(r'^Co-Authored-by:\s*(.+)$', re.IGNORECASE) |
| 321 | + |
| 322 | + # Process each line of the git output |
| 323 | + for line in output.split('\n'): |
| 324 | + author_match = author_regex.match(line) |
| 325 | + coauthor_match = coauthor_regex.match(line) |
| 326 | + |
| 327 | + # Handle author |
| 328 | + if author_match: |
| 329 | + author_info = author_match.group(1).strip() |
| 330 | + author_name = extract_name(author_info) |
| 331 | + if author_name: |
| 332 | + commit_counts[author_name] = commit_counts.get(author_name, 0) + 1 |
| 333 | + total_commits += 1 |
| 334 | + |
| 335 | + # Handle co-author |
| 336 | + elif coauthor_match: |
| 337 | + coauthor_info = coauthor_match.group(1).strip() |
| 338 | + coauthor_name = extract_name(coauthor_info) |
| 339 | + if coauthor_name: |
| 340 | + commit_counts[coauthor_name] = commit_counts.get(coauthor_name, 0) + 1 |
| 341 | + total_commits += 1 |
| 342 | + |
| 343 | + # Handle case if nothing is found |
| 344 | + if total_commits == 0: |
| 345 | + print("No commits found.") |
| 346 | + return |
| 347 | + |
| 348 | + # Prepare a list of contributors with counts and percentages |
| 349 | + contributors_list = [] |
| 350 | + for author, count in commit_counts.items(): |
| 351 | + percentage = (count / total_commits) * 100 |
| 352 | + contributors_list.append((count, author, percentage)) |
| 353 | + |
| 354 | + # Sort the list by commit count in descending order |
| 355 | + contributors_list.sort(key=lambda x: x[0], reverse=True) |
| 356 | + |
| 357 | + # Fancy stuff for making the commit count alignment kosher |
| 358 | + max_count = contributors_list[0][0] |
| 359 | + count_width = len(str(max_count)) + 1 # Extra space for alignment |
| 360 | + |
| 361 | + # Print all the fun stuff. Finally... |
| 362 | + print('Git commits per author:\n') |
| 363 | + for count, author, percentage in contributors_list: |
| 364 | + print(f"\t{count:<{count_width}} {author:<30} {percentage:5.1f}%") |
| 365 | + |
| 366 | + |
| 367 | +def extract_name(author_info: str) -> Optional[str]: |
| 368 | + """ |
| 369 | + Extracts the author's name from the author information string. |
| 370 | +
|
| 371 | + Args: |
| 372 | + author_info (str): The author information string (e.g., "Name <email@example.com>"). |
| 373 | +
|
| 374 | + Returns: |
| 375 | + Optional[str]: The extracted author name, or None if extraction fails. |
| 376 | + """ |
| 377 | + |
| 378 | + # Use regex to extract the name before the email |
| 379 | + # Mostly a helper function for commits |
| 380 | + # NOTE: Should we move this into a separate file with other helper funcs? |
| 381 | + match = re.match(r'^([^<]+)', author_info) |
| 382 | + if match: |
| 383 | + return match.group(1).strip() |
| 384 | + else: |
| 385 | + return None |
295 | 386 |
|
296 | 387 |
|
297 | 388 | def git_commits_per_date() -> None:
|
|
0 commit comments