Analyst git repo for loc
from collections import defaultdict
import subprocess
import sys


def run_git(args, repo_path):
  cmd = ["git", "-C", repo_path] + args
  result = subprocess.run(
      cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True
  )
  return result.stdout.strip()


def analyze_branches(repo_path):
  print("Discovering branches and analyzing code volume per branch...")
  raw_output = run_git(["branch", "-a", "--no-color"], repo_path)
  branches = set()

  for line in raw_output.splitlines():
    line = line.strip().replace("* ", "")
    if not line or "->" in line:
      continue
    if line.startswith("remotes/"):
      line = line[8:]
    branches.add(line)

  branch_stats = {}
  for branch in sorted(branches):
    try:
      files_out = run_git(["ls-tree", "-r", "--name-only", branch], repo_path)
      files = [f for f in files_out.splitlines() if f.strip()]
    except subprocess.CalledProcessError:
      continue

    total_lines = 0
    file_count = 0

    for file_path in files:
      show_cmd = ["git", "-C", repo_path, "show", f"{branch}:{file_path}"]
      try:
        raw_content = subprocess.run(
            show_cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=False,
            check=True,
        ).stdout

        # Skip binary files containing null bytes
        if b"\x00" in raw_content:
          continue

        lines = raw_content.count(b"\n")
        if raw_content and not raw_content.endswith(b"\n"):
          lines += 1

        total_lines += lines
        file_count += 1
      except subprocess.CalledProcessError:
        continue

    branch_stats[branch] = {"lines": total_lines, "files": file_count}

  return branch_stats


def analyze_developers(repo_path):
  print("Analyzing developer contribution history across all branches...")
  cmd = [
      "git",
      "-C",
      repo_path,
      "log",
      "--all",
      "--no-merges",
      "--numstat",
      "--format=AUTHOR:%an",
  ]

  try:
    result = subprocess.run(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
        check=True,
    )
  except subprocess.CalledProcessError as e:
    print(f"Error executing git log: {e.stderr.strip()}")
    return {}

  developer_stats = defaultdict(lambda: {"added": 0, "deleted": 0, "net": 0})
  current_author = "Unknown"

  for line in result.stdout.splitlines():
    line = line.strip()
    if not line:
      continue

    if line.startswith("AUTHOR:"):
      current_author = line[7:]
    else:
      parts = line.split("\t")
      if len(parts) >= 2:
        added, deleted = parts[0], parts[1]
        if added.isdigit() and deleted.isdigit():
          developer_stats[current_author]["added"] += int(added)
          developer_stats[current_author]["deleted"] += int(deleted)

  for author, stats in developer_stats.items():
    stats["net"] = stats["added"] - stats["deleted"]

  return developer_stats


if __name__ == "__main__":
  target_path = sys.argv[1] if len(sys.argv) > 1 else "."

  # --- Part 1: Branch Analysis ---
  branch_stats = analyze_branches(target_path)

  if branch_stats:
    print("\n" + "=" * 80)
    print("PART 1: CODE VOLUME BY BRANCH (Current HEAD of each branch)")
    print("=" * 80)
    print(f"{'Branch Name':<45} | {'Files':<8} | {'Total Lines':<12}")
    print("-" * 80)

    grand_total_lines = sum(data["lines"] for data in branch_stats.values())
    for branch, data in sorted(
        branch_stats.items(), key=lambda x: x[1]["lines"], reverse=True
    ):
      print(f"{branch:<45} | {data['files']:<8,} | {data['lines']:<12,}")

    print("=" * 80)
    print(f"Combined total lines across all branch HEADs: {grand_total_lines:,}")
    print("=" * 80)

  # --- Part 2: Developer History Analysis ---
  dev_stats = analyze_developers(target_path)

  if dev_stats:
    print("\n" + "=" * 95)
    print(
        "PART 2: DEVELOPER CONTRIBUTIONS ACROSS ALL HISTORY (All Branches,"
        " No Merges)"
    )
    print("=" * 95)
    print(
        f"{'Developer':<30} | {'Lines Added':<14} | {'Lines Deleted':<14} |"
        f" {'Net Lines':<12}"
    )
    print("-" * 95)

    sorted_devs = sorted(
        dev_stats.items(), key=lambda x: x[1]["added"], reverse=True
    )
    for author, data in sorted_devs:
      print(
          f"{author:<30} | {data['added']:<14,} | {data['deleted']:<14,}"
          f" | {data['net']:<12,}"
      )
    print("=" * 95)