Skip to content
Merged
Show file tree
Hide file tree
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
22 changes: 21 additions & 1 deletion src/gitfetch/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,27 @@ def _is_cache_expired(self, cached_at: datetime) -> bool:
Returns:
True if expired, False otherwise
"""
expiry_time = datetime.now() - timedelta(minutes=self.cache_expiry_minutes)
# Defensive handling: ensure cache_expiry_minutes is a reasonable int
# and avoid passing an extremely large integer to timedelta which can
# raise OverflowError on some platforms (assuming on 32-bit builds).
try:
minutes = int(self.cache_expiry_minutes)
except Exception:
minutes = 15

# Enforce sensible bounds: minimum 1 minute, cap to MAX_MINUTES
# (10 years expressed in minutes). This prevents OverflowError while
# still allowing very long cache durations when intentionally set.
MAX_MINUTES = 5256000 # 10 years
minutes = max(1, min(minutes, MAX_MINUTES))

try:
expiry_time = datetime.now() - timedelta(minutes=minutes)
except OverflowError:
# In the unlikely event timedelta still overflows, treat cache as
# non-expired (safe default) to avoid crashing the program.
return False

return cached_at < expiry_time

def list_cached_accounts(self) -> list[tuple[str, datetime]]:
Expand Down
14 changes: 13 additions & 1 deletion src/gitfetch/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,19 @@ def main() -> int:
return 0

except Exception as e:
print(f"Error: {e}", file=sys.stderr)
# When debugging, print full traceback to help diagnose issues
# (useful when users report errors from package builds / other
# environments where the short error message is not enough).
try:
import os
import traceback
if os.environ.get('GITFETCH_DEBUG'):
traceback.print_exc()
else:
print(f"Error: {e}", file=sys.stderr)
except Exception:
# Fallback to simple message if traceback printing fails
print(f"Error: {e}", file=sys.stderr)
return 1

except KeyboardInterrupt:
Expand Down
27 changes: 22 additions & 5 deletions src/gitfetch/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -951,10 +951,18 @@ def _build_month_line(self, weeks_data: list) -> str:

try:
date_obj = datetime.fromisoformat(first_day)
except ValueError:
# Validate year is in reasonable range to avoid C int overflow
if date_obj.year < 1900 or date_obj.year > 9999:
continue
except (ValueError, OverflowError):
continue

month_abbr = date_obj.strftime('%b')
try:
month_abbr = date_obj.strftime('%b')
except (ValueError, OverflowError):
# strftime can fail with years outside 1900-9999
continue

if month_abbr != last_month:
month_chars.append(month_abbr)
last_month = month_abbr
Expand Down Expand Up @@ -986,8 +994,11 @@ def _build_month_line_spaced(self, weeks_data: list) -> str:

try:
date_obj = datetime.fromisoformat(first_day)
# Validate year is in reasonable range to avoid C int overflow
if date_obj.year < 1900 or date_obj.year > 9999:
continue
current_month = date_obj.month
except ValueError:
except (ValueError, OverflowError):
continue

# Check if this is a new month
Expand All @@ -1003,6 +1014,9 @@ def _build_month_line_spaced(self, weeks_data: list) -> str:
prev_date_obj = datetime.fromisoformat(
prev_first_day
)
# Validate year is in reasonable range
if prev_date_obj.year < 1900 or prev_date_obj.year > 9999:
continue
prev_month = prev_date_obj.month
if current_month != prev_month:
# New month - add spacing and month name
Expand All @@ -1016,7 +1030,7 @@ def _build_month_line_spaced(self, weeks_data: list) -> str:
needed_space = max(1, calc)
month_line += " " * needed_space
month_line += month_name
except ValueError:
except (ValueError, OverflowError):
pass

return f" {month_line}"
Expand Down Expand Up @@ -1298,8 +1312,11 @@ def _format_date(self, date_string: str) -> str:
"""
try:
dt = datetime.fromisoformat(date_string.replace('Z', '+00:00'))
# Validate year is in reasonable range to avoid C int overflow
if dt.year < 1900 or dt.year > 9999:
return date_string
return dt.strftime('%B %d, %Y')
except (ValueError, AttributeError):
except (ValueError, AttributeError, OverflowError):
return date_string

def _get_contribution_block(self, count: int) -> str:
Expand Down