get_backup_branch_name() lists branches through list_heads() since
49e205ac. The legacy core passes GitPython's git.Repo, which has no
such method; the error was swallowed and the name was returned without
a collision check, so creating the backup branch failed when one of
the same second existed.
- Read the branches from heads when the repository has no list_heads()
- Repair the tests: the fake repository still exposed heads, so the
collision cases exercised the error fallback and failed
- Run the behaviour tests against both copies of the function, add a
real-repository test as each core opens it, and freeze the clock
140 lines
3.9 KiB
Python
140 lines
3.9 KiB
Python
"""
|
|
Robust timestamp utilities with datetime fallback.
|
|
|
|
Some environments (especially Mac) have issues with the datetime module
|
|
due to local file name conflicts or Homebrew Python module path issues.
|
|
"""
|
|
|
|
import logging
|
|
import time as time_module
|
|
import uuid
|
|
|
|
_datetime_available = None
|
|
_dt_datetime = None
|
|
|
|
|
|
def _init_datetime():
|
|
"""Initialize datetime availability check (lazy, once)."""
|
|
global _datetime_available, _dt_datetime
|
|
if _datetime_available is not None:
|
|
return
|
|
|
|
try:
|
|
import datetime as dt
|
|
if hasattr(dt, 'datetime'):
|
|
from datetime import datetime as dt_datetime
|
|
_dt_datetime = dt_datetime
|
|
_datetime_available = True
|
|
return
|
|
except Exception as e:
|
|
logging.debug(f"[ComfyUI-Manager] datetime import failed: {e}")
|
|
|
|
_datetime_available = False
|
|
logging.warning("[ComfyUI-Manager] datetime unavailable, using time module fallback")
|
|
|
|
|
|
def current_timestamp() -> str:
|
|
"""
|
|
Get current timestamp for logging.
|
|
Format: YYYY-MM-DD HH:MM:SS.mmm (or Unix timestamp if fallback)
|
|
"""
|
|
_init_datetime()
|
|
if _datetime_available:
|
|
return _dt_datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
|
|
return str(time_module.time()).split('.')[0]
|
|
|
|
|
|
def get_timestamp_for_filename() -> str:
|
|
"""
|
|
Get timestamp suitable for filenames.
|
|
Format: YYYYMMDD_HHMMSS
|
|
"""
|
|
_init_datetime()
|
|
if _datetime_available:
|
|
return _dt_datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
return time_module.strftime('%Y%m%d_%H%M%S')
|
|
|
|
|
|
def get_timestamp_for_path() -> str:
|
|
"""
|
|
Get timestamp for path/directory names.
|
|
Format: YYYY-MM-DD_HH-MM-SS
|
|
"""
|
|
_init_datetime()
|
|
if _datetime_available:
|
|
return _dt_datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
|
|
return time_module.strftime('%Y-%m-%d_%H-%M-%S')
|
|
|
|
|
|
def get_backup_branch_name(repo=None) -> str:
|
|
"""
|
|
Get backup branch name with current timestamp.
|
|
Format: backup_YYYYMMDD_HHMMSS (or backup_YYYYMMDD_HHMMSS_N if exists)
|
|
|
|
Args:
|
|
repo: Optional repository: a git_compat repository (glob core) or
|
|
GitPython's git.Repo (legacy core). If provided, checks for name
|
|
collisions and adds sequential suffix if needed.
|
|
|
|
Returns:
|
|
Unique backup branch name.
|
|
"""
|
|
base_name = f'backup_{get_timestamp_for_filename()}'
|
|
|
|
if repo is None:
|
|
return base_name
|
|
|
|
# Check if branch exists
|
|
try:
|
|
# a git_compat repository lists its branches with list_heads(); git.Repo has heads
|
|
heads = repo.list_heads() if hasattr(repo, 'list_heads') else repo.heads
|
|
existing_branches = {b.name for b in heads}
|
|
except Exception:
|
|
return base_name
|
|
|
|
if base_name not in existing_branches:
|
|
return base_name
|
|
|
|
# Add sequential suffix
|
|
for i in range(1, 100):
|
|
new_name = f'{base_name}_{i}'
|
|
if new_name not in existing_branches:
|
|
return new_name
|
|
|
|
# Ultimate fallback: use UUID (very unlikely to reach here)
|
|
return f'{base_name}_{uuid.uuid4().hex[:6]}'
|
|
|
|
|
|
def get_now():
|
|
"""
|
|
Get current datetime object.
|
|
Returns datetime.now() if available, otherwise a FakeDatetime object
|
|
that supports basic operations (timestamp(), strftime()).
|
|
"""
|
|
_init_datetime()
|
|
if _datetime_available:
|
|
return _dt_datetime.now()
|
|
|
|
# Fallback: return object with basic datetime-like interface
|
|
t = time_module.localtime()
|
|
|
|
class FakeDatetime:
|
|
def timestamp(self):
|
|
return time_module.time()
|
|
|
|
def strftime(self, fmt):
|
|
return time_module.strftime(fmt, t)
|
|
|
|
def isoformat(self):
|
|
return time_module.strftime('%Y-%m-%dT%H:%M:%S', t)
|
|
|
|
return FakeDatetime()
|
|
|
|
|
|
def get_unix_timestamp() -> float:
|
|
"""Get current Unix timestamp."""
|
|
_init_datetime()
|
|
if _datetime_available:
|
|
return _dt_datetime.now().timestamp()
|
|
return time_module.time()
|