Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions scripts/ownership_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@
import json
from typing import Dict, List, Any, Optional, Tuple
from collections import defaultdict

# All subprocess.run(..., text=True) calls below also pass
# encoding='utf-8', errors='replace'. Without an explicit encoding,
# subprocess decodes with the platform's default locale codec — cp1252 on
# Windows — which crashes with UnicodeDecodeError on git blame/log output
# containing non-cp1252 bytes (e.g. accented author names, non-ASCII commit
# text). Found via real-codebase validation: `history --check ownership`
# crashed with "'NoneType' object has no attribute 'split'" against a repo
# whose git history has UTF-8 characters outside cp1252's range.
from datetime import datetime, timezone
from utils import DEFAULT_IGNORE_DIRS, logger

Expand Down Expand Up @@ -74,7 +83,7 @@ def _is_git_repo(workspace: str) -> bool:
try:
result = subprocess.run(
['git', 'rev-parse', '--is-inside-work-tree'],
cwd=workspace, capture_output=True, text=True, timeout=5
cwd=workspace, capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=5
)
return result.returncode == 0
except (subprocess.SubprocessError, FileNotFoundError):
Expand All @@ -86,7 +95,7 @@ def _run_git_blame(workspace: str, file_path: str) -> Optional[List[Dict]]:
try:
result = subprocess.run(
['git', 'blame', '--line-porcelain', file_path],
cwd=workspace, capture_output=True, text=True, timeout=30
cwd=workspace, capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=30
)
if result.returncode != 0:
return None
Expand Down Expand Up @@ -280,7 +289,7 @@ def _analyze_workspace_ownership(workspace: str) -> Dict[str, Any]:
try:
result = subprocess.run(
['git', 'rev-parse', '--is-shallow-repository'],
cwd=workspace, capture_output=True, text=True, timeout=5
cwd=workspace, capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=5
)
if result.returncode == 0 and result.stdout.strip().lower() == 'true':
is_shallow = True
Expand All @@ -296,7 +305,7 @@ def _analyze_workspace_ownership(workspace: str) -> Dict[str, Any]:
# Get commit count per author
result = subprocess.run(
['git', 'shortlog', '-sn', '--all'],
cwd=workspace, capture_output=True, text=True, timeout=15
cwd=workspace, capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=15
)
if result.returncode == 0:
for line in result.stdout.strip().split('\n'):
Expand Down Expand Up @@ -343,7 +352,7 @@ def _analyze_workspace_ownership(workspace: str) -> Dict[str, Any]:
try:
result = subprocess.run(
['git', 'log', '-1', '--format=%ct', '--', rel_path],
cwd=workspace, capture_output=True, text=True, timeout=5
cwd=workspace, capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=5
)
if result.returncode == 0 and result.stdout.strip():
ts = int(result.stdout.strip())
Expand Down
Loading